I noticed java.lang.IllegalArgumentException results in an HttpStatus.INTERNAL_SERVER_ERROR when it reaches my REST controller, while I was expecting spring-boot to automatically map it to an HttpStatus.BAD_REQUEST.

You can use this code to recreate the issue:

@RestController
@RequestMapping("/v1/projects")
class ProjectController(val projectService: ProjectService) {
    @GetMapping
    @ResponseStatus(HttpStatus.OK)
    fun search(projectSearchDto: ProjectSearchDto): List<ProjectDto> {
        require(false)
        return projectService.filter(projectSearchDto)
    }
}

And you will notice you will get an HttpStatus.INTERNAL_SERVER_ERROR whenever you try to consume the API.

For the sake of completeness, this is my build.gradle.kts:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "3.0.3"
    id("io.spring.dependency-management") version "1.1.0"
    kotlin("jvm") version "1.7.22"
    kotlin("plugin.spring") version "1.7.22"
}

group = "xxx"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("jakarta.validation:jakarta.validation-api:3.0.2")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("de.flapdoodle.embed:de.flapdoodle.embed.mongo.spring30x:4.6.1")
    testImplementation("io.rest-assured:rest-assured:5.3.0")
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "17"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

Comment From: wilkinsona

There's no way of knowing in a general exception handler if the illegal argument is due to a client-side error (in which case a 400 response would be appropriate) or a server-side error (in which case a 500 response would be appropriate). I think your example is a good illustration of this. The require(false) is triggering the IllegalArgumentException and it has nothing to do with the request that's received from the client. In this case, a 500 is the correct response as there's nothing that the client can do about it.

Comment From: kandreak

You're definitely right. I have to point out I noticed it putting requirements on my dto, although, would it be any different? In that case the client would be responsible, but would there be any way for spring to know and act accordingly?

Comment From: wilkinsona

You may want to look at Spring Framework's validation support with which Spring MVC integrates. IIRC, any input into a controller method that fails validation will result in a 400 response being sent.

If you have any further questions, please follow up on Stack Overflow or Gitter. As mentioned in the guidelines for contributing, we prefer to use GitHub issues only for bugs and enhancements.