The other day I was working on a feature that required data to be submitted to a REST service. Even though the data is validated on the client-side, it is a good practice to also perform server-side validation. I have done that thousands of time on Spring MVC using the Bean Validation API… But can Spring handle @Valid annotations outside of MVC?
Yes it can!
Since version 3.1, Spring can actually validate a @RequestBody-annotated controller method argument in a similar fashion to what you would validate @ModelAttribute-annotated arguments in a Spring MVC application.
Let’s take the following bean as an example:
public class Movie { @NotEmpty private String name; ... getters and setters as usual... }
The bean’s property is annotated with @NotEmpty: a very useful annotation provided by Hibernate Validator‘s reference implementation of the Bean Validation API 1.1 (or JSR-349).
Now let’s imagine the following REST service implementation:
@RequestMapping("/movies") @RestController class MoviesWebServices { @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<?> save(@Valid @RequestBody Movie movie) { ... } }
Here we want to save a Movie whose JSON representation we receive when the service is invoked. But we want to make sure that it’s name is not empty, which is why we annotated the bean’s property above using @NotEmpty.
So when the service is invoked, Spring builds the Movie bean using its JSON representation, courtesy of MappingJackson2HttpMessageConverter (if Jackson is present on the class path!).
Then Spring triggers the validation of the reconstructed Movie bean, but only if it can find a JSR-303/JSR-349 provider such as Hibernate Validator on its class path.
My data is invalid… now what?
Let’s say the submitted movie data is missing the movie name. How does Spring inform you of the detected validation errors?
You can add a BindingResult argument right after the bean argument to be validated, exactly the same way you would do for model attributes in Spring MVC. The object is filled with the validation errors’ details and consequently we enter the method’s body, where you can then test the BindingResult object for errors and react accordingly.
@RequestMapping("/movies") @RestController class MoviesWebServices { @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<?> save(@Valid @RequestBody Movie movie, BindingResult bindingResults) { if (bindingResults.hasErrors()) { ...handle validation errors... } else { ...bean is valid! Save that movie!... } } }
But this also means that you have to test for validation errors inside every method whose argument needs to be validated. In other words: “with great power comes great… code duplication“. ‘Nuff said!
The other option is to let the validation process throw an exception, more specifically a MethodArgumentNotValidException. This way you can implement an exception handler for that specific exception and react accordingly. This is the typical REST service situation: when a validation issue is detected, the service sends back a 400 Bad Request HTTP code.
Here’s an example implementation of this:
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<?> save(@Valid @RequestBody Movie movie) { ... } @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseBody public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException e) { List<ObjectError> errors = e.getBindingResult().getAllErrors(); WebServiceError webServiceError = WebServiceError.build(WebServiceError.Type.VALIDATION_ERROR, errors.get(0).getObjectName() + " " + errors.get(0).getDefaultMessage()); return new ResponseEntity<>(webServiceError, HttpStatus.BAD_REQUEST); }
Every time a validation exception is raised, this bit of code kicks in and ensures you get back a decent error response.
Have you noticed that you can access the whole BindingResult object from that exception? The code above is very simplistic and only sends back the first error in the list… But since you have access to that BindingResult object, you can imagine wrapping all the validation errors into a bean and send that back using a ResponseEntity. The client will then receive a JSON that lists of all the validation errors detected at the server-side.
Isn’t that neat?
Testing my validated REST service using Spring Boot
If we really want to test this right, we probably need to submit a request to the REST service as if we were the client. Then we can check if the validation process kicked in or not by looking at what response comes out.
Intuitively, I’d like to run a test suite that would start my application programmatically and run the tests against that application by considering it as a “black-box“: send my REST request and see what comes back.
By writing our code on top of Spring Boot we are able to embed a Tomcat or Jetty server and programmatically run our application on it! Let’s try that out.
We start by setting up our pom.xml as follows:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>MyApp</name> <groupId>codesandnotes</groupId> <artifactId>myapp</artifactId> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.1.6.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <properties> <java.version>1.8</java.version> </properties> </project>
Pay attention to that spring-boot-starter-data-jpa dependency: it so happens that I’m using JPA for my project, and that also sets a dependency towards Hibernate Validator. That’s good, because Spring needs to have an implementation of the Bean Validation Framework in the class path for validation to work. If you are not using JPA in your project, make sure you add a dependency to Hibernate Validator or to any other valid JSR-303/JSR-349 provider!
We define our Spring Boot application using Java Configuration like this:
@ComponentScan @EnableAutoConfiguration public class Application { ...my beans and stuff... // Self-executing! public static void main(String[] args) { SpringApplication application = new SpringApplication(Application.class); application.run(args); } }
An important note here: when using XML we would normally add an <mvc:annotation-driven> element to our configuration file in order for Spring to take our @Valid annotations into account. To do the same with Java Configuration, we would use @EnableWebMvc. However if we rely on Spring Boot‘s @EnableAutoConfiguration this is taken care of already!
We now move to our test, which could look like:
public class MoviesTest { public static final String SAVE_URL = "http://localhost:8080/movies"; private RestTemplate restTemplate = new TestRestTemplate("user", "user"); @Test public void saveMovie() { Movie movie = new Movie(); // NO MOVIE NAME!!! ResponseEntity<?> response = restTemplate.postForEntity(SAVE_URL, movie, WebServiceError.class); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); // TODO: test WebServiceError's returned errors? } }
Don’t worry about that TestRestTemplate class: it’s a nice convenience class you get with Spring Boot.
If you run the test now you’ll get… most likely a ResourceAccessException! And that’s normal, because we’re trying to connect to a server (in fact our application) which is not running. So how do we make it start?
One solution is to extend these tests with a special class who starts our application. We should then have a running Jetty server (thanks to Spring Boot‘s starter POM!) on http://localhost:8080 to which we can send the requests to test:
public class AcceptanceTest { private static final ConfigurableApplicationContext APPLICATION_CONTEXT; static { // Run the application SpringApplication springApplication = new SpringApplication(Application.class); APPLICATION_CONTEXT = springApplication.run(); // Add shutdown hook Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { APPLICATION_CONTEXT.close(); } }); } }
The static code will run only once per test suite execution. It stores in a static field the application context returned by Spring Boot when running our SpringApplication. The application context is indeed required later on by the shutdown hook we register just below, so that we can close our programmatically-started application. Strictly speaking this last step is not mandatory, but I found it cleaner this way.
Oh, and let’s not forget to extend our test class:
public class MoviesTest extends AcceptanceTest { public static final String SAVE_URL = "http://localhost:8080/movies"; private RestTemplate restTemplate = new TestRestTemplate("user", "user"); @Test public void saveMovie() { ... } }
So now if we run our test we should see our beloved Spring Boot logo appear in the logs, initiating our application on its embedded Jetty server, then the test would send the request to it and hopefully obtain the right behavior: a validation error!
And we’re still in control
This is slightly off-topic, but anyway: now what if you want to access your programmatically-started application “from the inside“, for example to setup and clean the database for your acceptance tests?
Thankfully, Spring Boot allows you to access your application’s beans through the application context that is returned by its run() method. So you could imagine writing something like:
public class AcceptanceTest { private static final ConfigurableApplicationContext APPLICATION_CONTEXT; static { ... } public static void cleanupMyDB() { APPLICATION_CONTEXT.getBean(MovieDAO.class).truncate(); } }
The advantage is of course that you haven’t exposed that code through the official API: this database cleanup code is only accessible by your acceptance test, not by your application users. I can only imagine what would happen if that wasn’t the case…
That’s all for this time
Spring keeps on surprising me at times: I only recently discovered the possibility of leveraging @Valid to trigger the validation of my REST methods’ arguments, and that’s an absolute plus.
Combined with the simplicity with which, using Spring Boot, I can start my application on a Jetty server to black-box-test the validation process, I’d say it’s a winning combination for developing strong, reliable (and validated) REST services.
Any remarks? Spotted an error somewhere? Want to suggest an improvement? By all means, feel free to drop me a comment here below.
Cheers!
10 comments
Tajbeer Singh Rawat
22/08/2015 at 21:06what if you have write your custom message for @NotEmpty .I have tried messages.properties for Spring boot rest way but does not work.however it works for Spring MVC. Please let me know if you have a solution
codesandnotes
23/08/2015 at 11:25Hey there, I’ll be honest with you: I haven’t tried using custom messages for validation annotations for Spring Boot.
If it works on Spring MVC, then it’s probably Spring Boot trying to handle the static resources automatically and not finding your messages.properties file…
This should definitely be the topic of one of my future posts.
I’m sorry I don’t have a solution for you at the moment, but I’ll try to come up with one in a near future 🙁
Mannu
29/11/2015 at 07:13Hi Tajbeer, I am also trying to implement the same for custom annotations, I want to fetch error messages from message.properties file for Spring MVC. can you share your implementation ?
Mannu
30/11/2015 at 17:41Hi Tajbeer,
can you share your solution ? I tried message.properties for Spring MVC, but its not working.
Kalyan
30/09/2015 at 16:40WebServiceError webServiceError = WebServiceError.build(WebServiceError.Type.VALIDATION_ERROR, errors.get(0).getObjectName() + ” ” + errors.get(0).getDefaultMessage());
can you please give details of class WebServiceError
codesandnotes
30/09/2015 at 19:27Yes of course. It’s merely a java bean modeling the JSON to be sent back when an error occurs. It looks like this:
Joel
05/10/2015 at 16:05What if you want to require Movie.name during a POST but have it be an optional field during a PATCH?
codesandnotes
05/10/2015 at 16:42Hi Joel,
It all depends on what you are trying to achieve.
You will probably end up having two Java methods in your Spring controller: one that POSTs and another that PATCHes. You could disable validation on the PATCH method by omitting the @Valid annotation on Movie. But then of course all the validation by annotation on that Movie object would be disabled…
The simplest next thing I can think of would be to write two Movie data objects: remember, in an ideal word we use data objects in a controller, and once validated we convert them to matching “entities” (usually with Hibernate persistence annotations or whatnot). We could take advantage of that by implementing MovieForPost and MovieForPatch data objects. In one you make the movie name mandatory, and in the other one not. To keep it clean you could put common properties in a MovieCommon object and have MovieForPost and MovieForPatch extend that one…
Neha
05/10/2015 at 23:41Hello All,
How can I download this whole source code? Please share GIT link etc?
Thanks,
Neha
codesandnotes
06/10/2015 at 08:08Hello Neha,
No accompanying code for that blog post, I’m afraid! I usually write something on GitHub when discussing more complex matters…
Good news though: most of the background material discussed in the post, like creating REST web services with Spring or validating entities, can be found on the spring.io website. I merely explain that combining a REST controller and entity validation works!
Cheers!