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 theTypeMismatchException
thrown, set thebindingFailure
boolean tofalse
which throws aMethodArgumentNotValidException
mapped toHttpStatus.BAD_REQUEST
status code (400
).With WebFlux,
ModelAttributeMethodArgumentResolver#constructAttribute
, theTypeMismatchException
is thrown as it is without specific processing, leading to an http response with a500
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.