We just upgraded our Kotlin project from Spring Boot 2.2.4 to 2.2.5.

Unfortunately now some WebFlux-RestController tests fail because a mandatory JSON payload field now leads to a 500 Internal Server Error instead of a 400 Bad Request.

This is an exemplary RestController with its Kotlin data class for the request payload:

@RestController
class Controller {

    @PostMapping("/hello")
    fun post(@RequestBody request: RequestPayload) =
        ResponseEntity.ok().body("Hello ${request.foo} - ${request.bar}")

}

data class RequestPayload(
    val foo: String,
    val bar: String?
)

Please notice that attribute foo is mandatory (no ?).

This is the invalid payload (missing JSON field foo): { "bar": "bar text" }

When calling this RestController in a Spring Boot 2.2.4 app this results in a DecodingException which is correctly mapped to the status code 400.

But when calling this RestController in a Spring Boot 2.2.5 app this leads to CodecException which is not mapped to the status code 400 but results in a 500.

Please find the example in my GitHub repo: https://github.com/csh0711/boot-2-2-5-kotlin-codec-exception

Or use this ControllerTest:

@WebFluxTest(Controller::class)
internal class ControllerTests(
    @Autowired private val webTestClient: WebTestClient
) {
    @Test
    fun `responds with HTTP 400 BAD_REQUEST for missing foo in payload`() {
        webTestClient.post()
            .uri("/hello")
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(""" { "bar": "bar text" } """)
            .exchange()
            .expectStatus().isBadRequest
    }
}

Many thanks in advance! :-)

Comment From: gmariotti

This is happening also for WebMVC based projects

Comment From: csh0711

I took a closer look at the problem. This seems to be the place that is responsible for this behaviour:

Class AbstractJackson2Decoder

SB 2.2.4

private CodecException processException(IOException ex) {
    if (ex instanceof InvalidDefinitionException) {
        JavaType type = ((InvalidDefinitionException) ex).getType();
        return new CodecException("Type definition error: " + type, ex);
    }
    if (ex instanceof JsonProcessingException) {
        String originalMessage = ((JsonProcessingException) ex).getOriginalMessage();
        return new DecodingException("JSON decoding error: " + originalMessage, ex);
    }
    return new DecodingException("I/O error while parsing input stream", ex);
}

--> All JsonProcessingException are mapped to a DecodingException

SB 2.2.5

private CodecException processException(IOException ex) {
    if (ex instanceof MismatchedInputException) {  // specific kind of JsonMappingException
        String originalMessage = ((MismatchedInputException) ex).getOriginalMessage();
        return new DecodingException("Invalid JSON input: " + originalMessage, ex);
    }
    if (ex instanceof InvalidDefinitionException) {  // another kind of JsonMappingException
        JavaType type = ((InvalidDefinitionException) ex).getType();
        return new CodecException("Type definition error: " + type, ex);
    }
    if (ex instanceof JsonMappingException) {  // typically ValueInstantiationException
        String originalMessage = ((JsonMappingException) ex).getOriginalMessage();
        return new CodecException("JSON conversion problem: " + originalMessage, ex);
    }
    if (ex instanceof JsonProcessingException) {
        String originalMessage = ((JsonProcessingException) ex).getOriginalMessage();
        return new DecodingException("JSON decoding error: " + originalMessage, ex);
    }
    return new DecodingException("I/O error while parsing input stream", ex);   
}

--> The specialized JsonMappingException is mapped to a CodecException

Comment From: dreis2211

@csh0711 AbstractJackson2Decoder is a class in Spring-Framework and not in Boot itself.

The issues look closely related to the following issues there: - https://github.com/spring-projects/spring-framework/issues/24630 - https://github.com/spring-projects/spring-framework/issues/24610

Maybe the Boot team can transfer this in case it's a new variant of the above issues, but I guess otherwise it's gonna be fixed simply in Spring Framework.

Comment From: snicoll

Thanks @dreis2211, I agree. I've moved the issue to framework for further consideration. We might close it as duplicate if it turns out to be the same problem.

Comment From: csh0711

Okay, thanks @snicoll and @dreis2211 👍

Comment From: jhoeller

This is indeed the same problem, just in the reactive codec implementation as opposed to the HttpMessageConverter infrastructure (which are kept in sync in that respect). I'll keep this issue open as a variant of the same issue, closing it along with the others.