Validating the input of your REST API with Spring

In the next few weeks I will be writing a small web application and in these articles I’m going to explain certain aspects of it. Previous time I mentioned how you could secure your REST API (partially), and this time I will be talking about how to validate the input you send to the REST API. Don’t worry if you didn’t follow my previous tutorial, I will start from scratch again!

Since I’m going to start with a fresh Spring project, I’m going to choose for a Spring Boot project. To set up the project itself you can make the project manually, or you can use the online Initializr wizard. If you’re using Initializr, make sure you check Web, JPA and HSQLDB because I will be using those to demonstrate how to validate the input of a REST API.

initializr

After importing the project to your IDE you can start programming.

Defining our model

In this example I will be working with “an idea”, a model consisting out of 4 properties, the ID, a title, a description and the date it was created at. Similar to the previous tutorial, our model will eventually look like this:

package be.g00glen00b.model;

import java.util.Date;

import javax.persistence.*;
import javax.validation.constraints.*;

@Entity
@Table
public class Idea {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;
  @Column
  private String title;
  @Column
  private String description;
  @Column
  private Date createdAt;

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public Date getCreatedAt() {
    return createdAt;
  }

  public void setCreatedAt(Date createdAt) {
    this.createdAt = createdAt;
  }

  public long getId() {
    return id;
  }
}

Persisting data

The next step is to define the Spring Data JPA repository. This part is quite easy, because defining a repository with Spring Data is as simple as extending from an interface, for example:

@Repository
public interface IdeaRepository extends JpaRepository<Idea , Long> {

}

Writing the controller

To serve our REST services, we have to create a controller using Springs @RestController annotation. This is similar to the normal @Controller annotation, with one exception; it automatically treats all response objects as the actual response.

A controller for basic CRUD operations using Spring could be something like this:

package be.g00glen00b.controller;


import java.util.*;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import be.g00glen00b.model.Idea;
import be.g00glen00b.repository.IdeaRepository;

@RestController
@RequestMapping("/api/ideas")
public class IdeaController {
  @Autowired
  private IdeaRepository repository;

  @RequestMapping(method = RequestMethod.GET)
  public List<Idea> findAll() {
    return repository.findAll();
  }
  
  @RequestMapping(method = RequestMethod.POST)
  public Idea add(@RequestBody Idea idea) {
    Idea model = new Idea();
    model.setCreatedAt(new Date());
    model.setTitle(idea.getTitle());
    model.setDescription(idea.getDescription());
    return repository.saveAndFlush(model);
  }
  
  @RequestMapping(value = "/{id}", method = RequestMethod.GET)
  public Idea findOne(@PathVariable long id) {
    return repository.findOne(id);
  }
  
  @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
  public Idea update(@PathVariable long id, @RequestBody Idea idea) {
    Idea model = repository.findOne(id);
    if (model != null) {
      model.setTitle(idea.getTitle());
      model.setDescription(idea.getDescription());
      return repository.saveAndFlush(model);
    }
    return null;
  }
  
  @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
  public void delete(@PathVariable long id) {
    repository.delete(id);
  }
}

Testing it out

This example looks fine except that there’s one problem… there is almsot no validation. You can easily add ideas that have no title (which should be required) or you can enter a title/description that has any length, eventually surpassing the database limits resulting in unexpected exceptions/behavior.

In real code you will probably split this further into a proper DTO (so that you don’t have to use your entity) and a proper service that contains all the logic that we now wrote in our controller.

In fact, we can try it out already. Just open application.properties first and add the following line:

spring.jpa.generate-ddl=true

Since we added HSQLDB to our dependencies, Spring Boot will automatically set up the entity manager to use an in memory HSQLDB. The only thing that we have to do is to say that it has to generate proper tables based upon our model/entities.

There are a lot of properties that can be configured this way. Make sure to check out Appendix A to see a list of commonly used properties.

Anyways, run your application as a Spring Boot application, either by running the main Application class as a Java application or by running the following command:

mvn spring-boot:run

You could also use the Spring Tool Suite (STS) as an Eclipse plugin or standalone, allowing you to directly run the project as a Spring Boot project.

Now open a REST client and add an idea using the REST API:

As you can see our idea was added successfully… without a title though.

Validating our model

Validating our data is quite easily. You could manually write several if-statements to check whether or not an idea is valid, but you can also use Java bean validations to check the validity of the model. So, let’s open the Idea and add the following annotations to the title property:

@Column
@NotNull
@Size(min = 1, max = 30)
private String title;

And also add the following annotation to the description:

@Size(max = 100)

The @NotNull annotation guarantees that the input should not be equal to null. With the @Size annotation on the other hand we can tell the min/max size of the field, in this case the title should be at least 1 character and at most 30 characters, while the description has no minimal length but should not be longer than 100 characters.

Telling Spring when to validate

Now all we have to do is to indicate where we want to validate our beans using the @Validated annotation. Open IdeaController and add the annotation everywhere you see the @RequestBody annotation, for example:

@RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public Idea update(@PathVariable long id, @Validated @RequestBody Idea idea) {
  Idea model = repository.findOne(id);
  if (model != null) {
    model.setTitle(idea.getTitle());
    model.setDescription(idea.getDescription());
    return repository.saveAndFlush(model);
  }
  return null;
}

And also the add method:

@RequestMapping(method = RequestMethod.POST)
public Idea add(@Validated @RequestBody Idea idea) {
  Idea model = new Idea();
  model.setCreatedAt(new Date());
  model.setTitle(idea.getTitle());
  model.setDescription(idea.getDescription());
  return repository.saveAndFlush(model);
}

Testing it out… again

If we run our application now and try to enter the same data as before, we will see it’s no longer possible:

invalid-post

That’s already pretty good… but the error message could be improved:

Validation failed for argument at index 0 in method: public be.g00glen00b.model.Idea be.g00glen00b.controller.IdeaController.add(be.g00glen00b.model.Idea), with 1 error(s): [Field error in object 'idea' on field 'title': rejected value [null]; codes [NotNull.idea.title,NotNull.title,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [idea.title,title]; arguments []; default message [title]]; default message [error.title.notnull]]

I don’t like it to have this kind of error messages, so how about we customize them?

Back to the drawing tables

First of all, let’s get back to the Idea entity and check the annotations. Each validation annotation allows you to add a message property, so let’s add something like this:

@Column
@NotNull(message = "error.title.notnull")
@Size(min = 1, max = 30, message = "error.title.size")
private String title;
@Column
@Size(max = 100, message = "error.description.size")
private String description;

The plan is to have these messages translated to a proper error message using Spring’s MessageSource (Spring i18n).

So, the next step is to make a MessageSource bean by adding one to SpringValidationApplication (the main class). For example:

@Bean(name = "messageSource")
public ReloadableResourceBundleMessageSource messageSource() {
  ReloadableResourceBundleMessageSource messageBundle = new ReloadableResourceBundleMessageSource();
  messageBundle.setBasename("classpath:messages/messages");
  messageBundle.setDefaultEncoding("UTF-8");
  return messageBundle;
}

This tells Spring to look at the messages folder for files starting with messages. For example, if you’re using the NL (Dutch) locale, Spring will first look for messages/messages_nl.properties and if it couldn’t find anything it would be looking at messages/messages.properties.

I’m not going to implement multiple languages right now, so I’m just going to add a file called messages.properties inside src/main/resources/messages:

error.title.notnull=The title is a required field
error.title.size=The title should be between 1 and 30 characters
error.description.size=The description should be limited to 100 characters

Using our customized messages

The last step is to make it work so that our custom messages are used in stead of the standard error message. If we look back at the errormessage from last time, we notice that it was actually caused by a MethodArgumentNotValidException exception.

With Spring you can write exception handlers that will intercept exceptions of a specific kind and return something else. First of all… we need something to be returned. So let’s create a class called MessageDTO:

package be.g00glen00b.dto;

public class MessageDTO {
  private String message;
  private MessageType type;
  
  public MessageDTO() {
    super();
  }
  
  public MessageDTO(MessageType type, String message) {
    super();
    this.message = message;
    this.type = type;
  }

  public String getMessage() {
    return message;
  }
  
  public void setMessage(String message) {
    this.message = message;
  }
  
  public MessageType getType() {
    return type;
  }
  
  public void setType(MessageType type) {
    this.type = type;
  }
}

This class has two properties, a message type and the message itself. The MessageType will be an enumeration containing the possible message types:

public enum MessageType {
  SUCCESS, INFO, WARNING, ERROR
}

Finally, we have to write our controller advice which will intercept the exceptions:

@ControllerAdvice
public class ControllerValidationHandler {
  @Autowired
  private MessageSource msgSource;

  @ExceptionHandler(MethodArgumentNotValidException.class)
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ResponseBody
  public MessageDTO processValidationError(MethodArgumentNotValidException ex) {
    BindingResult result = ex.getBindingResult();
    FieldError error = result.getFieldError();

    return processFieldError(error);
  }

  private MessageDTO processFieldError(FieldError error) {
    MessageDTO message = null;
    if (error != null) {
      Locale currentLocale = LocaleContextHolder.getLocale();
      String msg = msgSource.getMessage(error.getDefaultMessage(), null, currentLocale);
      message = new MessageDTO(MessageType.ERROR, msg);
    }
    return message;
  }
}

So, having a class annotated with @ControllerAdvice means that it will automatically be applied to each controller. You could write a separate @ExceptionHandler for each controller, but this way you only have to write it once.

What happens here is that when we have an exception of the MethodArgumentNotValidException type, it will respond with HTTP Status 400 (Bad request) and it will have a responsebody of type MessageDTO containing the error message from our MessageSource.

I chose to use result.getFieldError() to show only a message for the first error. If you want to handle multiple errors, then use the getFieldErrors() method and just loop over it and respond with a list of MessageDTO.

The final test

We started out from an unvalidated service, to a service with basic validation and finally we will test our customized messages.

Run the application again and send an invalid response. If everything works correctly you should see a response similar to the one below.

invalid-post-custom

Try out the various error messages by setting an empty title, a title that’s too long or a description that’s too long:

invalid-post-custom-2

invalid-post-custom-3

invalid-post-custom-4

Achievement: Validated the input of your REST API with Spring

If you’re seeing this, then it means you successfully managed to make it through this article. If you’re interested in the full code example, you can find it on GitHub. If you want to try out the code yourself, you can download an archive from GitHub.

Tagged , , .

g00glen00b

IT Consultant with a passion for JavaScript. Experienced in the Spring Framework and various JavaScript frameworks.

  • Atul Pandey

    how can i use ConstraintViolationExcepption instead of

    MethodArgumentNotValidException

  • firstpostcommenter

    I am not able to validate @PathVariable using this method

  • Sabz

    Thanks for this detailed explanation. I have one question if you don’t mind, lets say I want to display an errorCode beside the message I’m responding with, not only message and type. So for ‘error.title.notnull’ I want to assign an errorCode of -1, and display that in the response. How is that achievable? I tried to look for arguments inside @NotNull and it have something called `payload` but I’m not sure if I can leverage that. Any ideas?

    • I’m not sure if you can do that with the payload, however, you can also just come up with some kind of a map that contains the key error.title.notnull and the value would be an integer with your error code. Within your exception handler you can use that map to get the error code for error.title.notnull.

  • mamoulian

    Is there a way to default every mapping argument to @Validated rather than having to add it individually to each one?

    • I don’t think it’s possible. According to the docs you can put the @Validated annotation on a class, but I haven’t found any examples using it that way.

  • Nishanth Damodharan

    if i am using Spring RestTemplate for consuming this, I have to specify in resttemplate query method as a parameter the type of the object returned. In this case the rest api call could return different pojos depending on validation. So whats the solution for that?
    please help

    • Well, when we use RestTemplate we create our own DefaultResponseErrorHandler and configure it using restTemplate.setErrorHandler().

      Inside the handleError() method you can do whatever you want, if you work with a similar approach as me, you could use:

      @Component
      public class MessageErrorHandler extends DefaultResponseErrorHandler {
      @Autowired
      private ObjectMapper mapper;
      private Logger logger = LoggerFactory.getLogger(getClass());

      @Override
      public boolean hasError(ClientHttpResponse response) throws IOException {
      return super.hasError(response);
      }

      @Override
      public void handleError(ClientHttpResponse response) throws IOException {
      try {
      MessageDTO message = mapper.readValue(response.getBody(), MessageDTO.class);
      throw new IlegalArgumentException(message.getMessage()); // We throw an exception with the message text
      } catch (JsonMappingException ex) {
      logger.info(“Message error could not be resolved”, ex);
      super.handleError(response);
      }
      }

      }

  • NextRandom

    Hi g00glen00b ,
    great tutorial , very helpful , I’as looking for such kind of solution , but i have one question on how can we use same
    ControllerValidationHandler for other controller as well, do we need to create separate messages.properties file also .

    Basically how can i use same ControllerValidationHandler & property file for every Domain data validation

    • Well, due to the @ControllerAdvice annotation on the ControllerValidationHandler, it will already be applied to all other controllers as well. You don’t need a separate messages.properties either, just make sure you use unique property names then. In the example I used error.title.notnull, but you can easily rename this to error.idea.title.notnull and create other properties in the same messages.properties file, such as error.anotherclass.title.notnull.

      • NextRandom

        well this solve my property file issue, let me check this way
        thank you and i really appreciate your proactive reply
        Have a great day man

  • Luca Botti

    Hi
    what are you using for testing the Call? A chrome Extension or a standalone app?

  • Ramesh

    Thank you very much. It is working for me. Can I change the Status from 400 to 200 also.

    • Sure, I added the @ResponseStatus(HttpStatus.BAD_REQUEST) annotation, but you can also use @ResponseStatus(HttpStatus.OK) or @ResponseStatus(HttpStatus.SUCCESS) (no idea how it’s called).

  • abhinav

    what if i just want to validate a requestparam?

    • I haven’t tested it, but the bean validation annotations (@NotNull, @Size, …) can be used on parameters as well.

  • jarus

    This is exactly what I wanted to do. Thank you for your post stranger.

  • Pingback: Eat Java Drink Java | Codinko()

  • Diego Squillaci

    Great, thank you.