1

I am replacing manual validation of input to a POST request in a Spring Boot REST-controller. JSR-303 Spring Bean Validation is used for validating the instance variables in the request body and this is working as expected. What is the recommended method to validate that the object in the request body is not null?

I have tried:

  • annotating the entire object such as this: @NotNull @Valid @RequestBody Foo foo

  • annotating the entire class with @NotNull

I am replacing:

@PostMapping...
public ResponseEntity<Map<String, Object>> editFoo(
@RequestBody Foo foo, ...) {
   if(foo == null) {
      return (new ResponseEntity<>(headers, HttpStatus.BAD_REQUEST));
   }
}

with a Bean Validation equivalent:

@PostMapping...
public ResponseEntity<Map<String, Object>> editFoo(
@Valid @RequestBody Foo foo, ...) {
   ...
}

I tried unit testing the controller method by:

// Arrange
    Foo foo = null;
    String requestBody = objectMapper.writeValueAsString(foo);

    // Act + assert
    mockMvc
    .perform(
    post("/end_point")
    .contentType("application/json")
    .content(requestBody))
    .andExpect(status().isBadRequest());

I expected a MethodArgumentNotValidException which is handled by a @ControllerAdvice for this exception, but I get HttpMessageNotReadableException when executing the unit test. My questions:

  1. is it necessary to test if the request body is null?
  2. if 1. is true, how should this be done with Bean Validation?
mstro
  • 13
  • 1
  • 1
  • 5

1 Answers1

2

Seeing your code, you already check if the body is null. In fact @RequestBody has a default parameter required which defaults to true. So no need for Bean validation for that !

Your main issue here seems to be in your test. First of all it is good to write a test to validate your endpoint behavior on null.

However, in your test you does not pass null. You try to create a Json object from a null value with your objectMapper. The object you are writting seems not to be a valid json. So when your sending this body, Spring says that it cannot read the message, aka the body of your request, as you say it is a application/json content but there is not json in it.

To test null body, just send your request in your test just removing the .content(requestBody) line and it should work !

--- Edit 1 I thought it was rejecting the message because of the body, but in fact it seems to work right away for me. Here is my controler and test so you can compare to your full code :

@RestController()
@RequestMapping("end_point")
public class TestController {

    @PostMapping
    public ResponseEntity<Map<String, Object>> editFoo(@RequestBody Foo foo) {
       // if(foo == null) {
       //     return (new ResponseEntity<>(new HashMap<>(), HttpStatus.BAD_REQUEST));
       // }

        return (new ResponseEntity<>(new HashMap<>(), HttpStatus.OK));
    }
}


@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class TestControllerTest {
    @Autowired
    private MockMvc mvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    public void test_body_is_null() throws Exception {

        Foo foo = null;
        String requestBody = objectMapper.writeValueAsString(foo);

        // Act + assert
        mvc
            .perform(
                post("/end_point")
                    .contentType("application/json")
                    .content(requestBody))
            .andExpect(status().isBadRequest());
    }
}

This was made using Spring Boot 2.1.6.RELEASE

--- Edit 2 For the record if you want to use validation for null here, here is a snippet of the controller :

@RestController()
@RequestMapping("end_point")
@Validated
public class TestController {

    @PostMapping
    public ResponseEntity<Map<String, Object>> editFoo(@NotNull @RequestBody(required = false) Foo foo) {

        return (new ResponseEntity<>(new HashMap<>(), HttpStatus.OK));
    }
}

First you have to set required to false for the body, as default is true. Then you have to add the @NotNull annotation on the request body and @Validated on the controller. Here if you launch your test you will see that the request fails with :

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is javax.validation.ConstraintViolationException: editFoo.foo: must not be null

As you said you had a @ControllerAdvice you can then map the exception as you wish !

A Le Dref
  • 422
  • 3
  • 7
  • Thank you for the quick reply! I am still getting HttpMessageNotReadableException as the exception when emitting ```.content(requestbody)``` so I guess I'm wondering whether to handle the HttpMessageNotReadableException with an ExceptionHandler or try to get the method to throw MethodArgumentNotValidException so that the exception is caught by the @ControllerAdvice! – mstro Jul 15 '19 at 11:24
  • This is strange ... if you add the controller I made into your project code does the test pass on it ? Do you have other endpoint that uses json as content type ? Maybe your Jackson deserializer has been customized and does not recognize null as json ? I encountered once issues while using xml and json on the same API with jackson ... Seeing the thrown exception, deserializing must be the issue here – A Le Dref Jul 15 '19 at 12:20
  • It throws `HttpMessageNotReadableException` which seems to be the default behaviour when sending null as the request body. This exception returns a http response with status code 400 which is the expected http response. Edit: Jackson serializer is not customized – mstro Jul 15 '19 at 14:28