Search code examples
javaspring-bootspring-mvcjacksonlombok

Why does @EnableWebMvc break the JSON deserialization?


In my sample application I have a simple @RestController controller:

package app.springtest.api.book;

import app.springtest.service.BookService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RestController
@RequestMapping("api/v1/book")
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class BookController {

    private final BookService bookService;

    @PostMapping
    public ResponseEntity upsertBook(@Valid @RequestBody BookRequest bookRequest) {
        final BookResponse response = bookService.addBook(bookRequest);
        return ResponseEntity.ok().body(response);
    }
}

It consumes this Json object.

package app.springtest.api.book;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Wither;

import javax.validation.constraints.NotBlank;

@Data
@RequiredArgsConstructor(onConstructor = @__(@JsonCreator))
@Wither
public class BookRequest {

    @NotBlank
    @JsonProperty(value = "isbn", required = true)
    private final String isbn;

    @NotBlank
    @JsonProperty(value = "name", required = true)
    private final String name;

    @JsonProperty(value = "author", required = true)
    @NotBlank
    private final String author;

}

There is nothing special, just Lombok generating some boilerplate code.

However, when I add @EnableWebMvc annotation to the application configuration, the post request fails and returns this error

{
    "timestamp": 1557494204976,
    "status": 415,
    "error": "Unsupported Media Type",
    "message": "Content type 'application/json;charset=UTF-8' not supported",
    "path": "/api/v1/book"
}

and there is following error in log:

.c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class app.springtest.api.book.BookRequest]]: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type app.springtest.api.book.BookRequest: Argument #0 has no property name, is not Injectable: can not use as Creator [constructor for app.springtest.api.book.BookRequest, annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}]

How can it be, that the deserialization stops working?

Edit: And why does the @RestController class work in the situation, when the @EnableWebMvc is not present? Isn't this the annotation, that makes MVC work in Spring?


Solution

  • When you rely on spring boot autoconfiguration, it automatically discovers custom JsonSerializer, Converter and many other spring mvc stuff. This is handled in WebMvcAutoConfiguration. This autoconfiguration is usually triggered through @SpringBootApplication

    As soon as you add @EnableWebMvc (notice the package name, which comes from spring-web), it will import DelegatingWebMvcConfiguration.

    @Import({DelegatingWebMvcConfiguration.class})
    public @interface EnableWebMvc {
    }
    

    As soon as this configuration is imported, the annotation @ConditionalOnMissingBean on WebMvcAutoConfiguration will no longer be truthy. Therefore, no more autoconfiguration of Spring MVC.

    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    public class WebMvcAutoConfiguration {
    

    A great way to debug this kind of behavior is to enable debug logging in application.yaml

    debug: true
    

    You will then get those kind of reports: Conditional missing bean