I am building java application using Spring Boot 2.7.12 And I have very simpler REST API controller like that:

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController


@RestController
@RequestMapping("/sample")
class SampleController {

    data class Arguments (
        val intValue: Int
    )


    @GetMapping("/echo")
    fun resource(args: Arguments) = "Return back: ${args.intValue}"
}

And the issue when I am making a regular regular request as expected, everything works fine:

$ curl http://localhost:8080/sample/echo?intValue=111
Return back: 111

However if I will enter non-numeric value, Spring Boot is crashing:

$ curl http://localhost:8080/sample/echo?intValue=111aaa
{"timestamp":"2023-07-27T16:03:34.023+00:00","path":"/sample/echo","status":500,"error":"Internal Server Error","requestId":"de11f158-6"}

If it is non-valid input, it should rather be 400 Bad Requests, than 500 Internal errors, and such things should be handled by the framework.

We using the Spring Boot framework, because we need some framework that will handle all edge case scenarios with mapping of the HTTP request parameters to controller input arguments, and that obviously should be handled properly by the framework.

Could you please advise, how to handle such HTTP requests properly?


Steps to reproduce Download attach sample project: demo.zip

Run following command:

echo $JAVA_HOME
echo 
./gradlew clean build


java -jar ./build/libs/demo-0.0.1-SNAPSHOT.jar >logs &
jdpid=$(echo $!)
echo $jdpid

sleep 10

echo
echo '================================================================'
echo 'Call sample/echo?intValue=1111, return 200 as expected'
echo 
curl -v http://localhost:8080/sample/echo?intValue=1111;

echo
echo '================================================================'
echo 'Call sample/echo?intValue=1111aaa, Returns 500 but should 400'
echo
curl -v http://localhost:8080/sample/echo?intValue=1111aaaa;

kill $jdpid

echo
echo 'To see log type `less logs`'

Listing output:

C:\Program Files\OpenJDK\jdk-20.0.2


Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/8.2.1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD SUCCESSFUL in 6s
8 actionable tasks: 7 executed, 1 up-to-date
[1] 1317
1317

================================================================
Call sample/echo?intValue=1111, return 200 as expected

*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /sample/echo?intValue=1111 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 17
<
Return back: 1111* Connection #0 to host localhost left intact

================================================================
Call sample/echo?intValue=1111aaa, Returns 500 but should 400

*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /sample/echo?intValue=1111aaaa HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 500 Internal Server Error
< Content-Type: application/json
< Content-Length: 137
<
{"timestamp":"2023-08-13T15:14:29.742+00:00","path":"/sample/echo","status":500,"error":"Internal Server Error","requestId":"292fce01-2"}* Connection #0 to host localhost left intact

To see log type `less logs`

Stack trace: log.txt

Comment From: sdeleuze

I can indeed reproduce with the repro provided with WebFlux but not with WebMVC.

With WebMvc, ModelAttributeMethodProcessor#constructAttribute catches the TypeMismatchException thrown, set the bindingFailure boolean to false which throws a MethodArgumentNotValidException mapped to HttpStatus.BAD_REQUEST status code (400).

With WebFlux, ModelAttributeMethodArgumentResolver#constructAttribute, the TypeMismatchException is thrown as it is without specific processing, leading to an http response with a 500 status code.

As far as I can tell, it does not look like Kotlin specific.

Comment From: amidukr

I can indeed reproduce with the repro provided with WebFlux but not with WebMVC.

With WebMvc, ModelAttributeMethodProcessor#constructAttribute catches the TypeMismatchException thrown, set the bindingFailure boolean to false which throws a MethodArgumentNotValidException mapped to HttpStatus.BAD_REQUEST status code (400).

With WebFlux, ModelAttributeMethodArgumentResolver#constructAttribute, the TypeMismatchException is thrown as it is without specific processing, leading to an http response with a 500 status code.

As far as I can tell, it does not look like Kotlin specific.

When I've used Java POJO instead of Kotlin Data class, it works well and returns 400 instead of crashing with 500, so it related to Kotlin data class, which quite bit different to POJO.

@RestController
@RequestMapping("/sample")
class SampleController {

    data class Arguments(val intValue: Int) {
        class  Pojo{
            var intValue: Int = -1

            fun toData() = Arguments(intValue =  this.intValue)
        }
    }

    @GetMapping("/echo")
    fun resource(args: Arguments.Pojo) = "Return back: ${args.toData().intValue}"
}

Comment From: sdeleuze

If the class have a single default constructor, then ModelAttributeMethodArgumentResolver#resolveArgument does proper exception handling by catching binding errors and throwing a WebExchangeBindException, but if the class can be initialized via its constructor, then you get the same 500 status code, either with Kotlin or Java.