If you have a few years of experience in the Java ecosystem, and you're interested in sharing that experience with the community (and getting paid for your work of course), have a look at the "Write for Us" page. Cheers. Eugen

I just announced the new Spring Boot 2 material, coming in REST With Spring:

>> CHECK OUT THE COURSE

1. Overview

This article covers a basic introduction to Spring Data REST Validators. If you need to first go over the basics of Spring Data REST, definitely visit this article to brush up on the basics.

Simply put, with Spring Data REST, we can simply add a new entry into the database through the REST API, but we of course also need to make sure the data is valid before actually persisting it.

This article continues on an existing article and we will reuse the existing project we set up there.

2. Using Validators

Starting with Spring 3, the framework features the Validator interface – which can be used to validate objects.

2.1. Motivation

In the previous article, we defined our entity having two properties – name and email.

And so, to create a new resource, we simply need to run:

curl -i -X POST -H "Content-Type:application/json" -d 
  '{ "name" : "Test", "email" : "[email protected]" }' 
  http://localhost:8080/users

This POST request will save the provided JSON object into our database, and the operation will return:

{
  "name" : "Test",
  "email" : "[email protected]",
  "_links" : {
    "self" : {
        "href" : "http://localhost:8080/users/1"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/1"
    }
  }
}

A positive outcome was expected since we provided valid data. But, what will happen if we remove the property name, or just set the value to an empty String?

To test the first scenario, we will run modified command from before where we will set empty string as a value for property name:

curl -i -X POST -H "Content-Type:application/json" -d 
  '{ "name" : "", "email" : "Baggins" }' http://localhost:8080/users

With that command we’ll get the following response:

{
  "name" : "",
  "email" : "Baggins",
  "_links" : {
    "self" : {
        "href" : "http://localhost:8080/users/1"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/1"
    }
  }
}

For the second scenario, we will remove property name from request:

curl -i -X POST -H "Content-Type:application/json" -d 
  '{ "email" : "Baggins" }' http://localhost:8080/users

For that command we will get this response:

{
  "name" : null,
  "email" : "Baggins",
  "_links" : {
    "self" : {
        "href" : "http://localhost:8080/users/2"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/2"
    }
  }
}

As we can see, both requests were OK and we can confirm that with 201 status code and API link to our object.

This behavior is not acceptable since we want to avoid inserting partial data into a database.

2.2. Spring Data REST Events

During every call on Spring Data REST API, Spring Data REST exporter generates various events which are listed here:

  • BeforeCreateEvent
  • AfterCreateEvent
  • BeforeSaveEvent
  • AfterSaveEvent
  • BeforeLinkSaveEvent
  • AfterLinkSaveEvent
  • BeforeDeleteEvent
  • AfterDeleteEvent

Since all events are handled in a similar way, we will only show how to handle beforeCreateEvent which is generated before a new object is saved into the database.

2.3. Defining a Validator

To create our own validator, we need to implement the org.springframework.validation.Validator interface with the supports and validate methods.

Supports checks if the validator supports provided requests, while validate method validates provided data in requests.

Let’s define a WebsiteUserValidator class:

public class WebsiteUserValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return WebsiteUser.class.equals(clazz);
    }

    @Override
    public void validate(Object obj, Errors errors) {
        WebsiteUser user = (WebsiteUser) obj;
        if (checkInputString(user.getName())) {
            errors.rejectValue("name", "name.empty");
        }
   
        if (checkInputString(user.getEmail())) {
            errors.rejectValue("email", "email.empty");
        }
    }

    private boolean checkInputString(String input) {
        return (input == null || input.trim().length() == 0);
    }
}

Errors object is a special class designed to contain all errors provided in validate method. Later in this article, we’ll show how you can use provided messages contained in Errors object.
To add new error message, we have to call errors.rejectValue(nameOfField, errorMessage).

After we’ve defined the validator, we need to map it to a specific event which is generated after the request is accepted.

For example, in our case, beforeCreateEvent is generated because we want to insert a new object into our database. But since we want to validate object in a request, we need to define our validator first.

This can be done in three ways:

  • Add Component annotation with name “beforeCreateWebsiteUserValidator“. Spring Boot will recognize prefix beforeCreate which determines the event we want to catch, and it will also recognize WebsiteUser class from Component name.
    @Component("beforeCreateWebsiteUserValidator")
    public class WebsiteUserValidator implements Validator {
        ...
    }
  • Create Bean in Application Context with @Bean annotation:
    @Bean
    public WebsiteUserValidator beforeCreateWebsiteUserValidator() {
        return new WebsiteUserValidator();
    }
  • Manual registration:
    @SpringBootApplication
    public class SpringDataRestApplication 
      extends RepositoryRestConfigurerAdapter {
        public static void main(String[] args) {
            SpringApplication.run(SpringDataRestApplication.class, args);
        }
    
        @Override
        public void configureValidatingRepositoryEventListener(
          ValidatingRepositoryEventListener v) {
            v.addValidator("beforeCreate", new WebsiteUserValidator());
        }
    }
    • For this case, you don’t need any annotations on WebsiteUserValidator class.

2.4. Event Discovery Bug

At the moment, a bug exists in Spring Data REST – which affects events discovery.

If we call POST request which generates the beforeCreate event, our application will not call validator because the event will not be discovered, due to this bug.

A simple workaround for this problem is to insert all events into Spring Data REST ValidatingRepositoryEventListener class:

@Configuration
public class ValidatorEventRegister implements InitializingBean {

    @Autowired
    ValidatingRepositoryEventListener validatingRepositoryEventListener;

    @Autowired
    private Map<String, Validator> validators;

    @Override
    public void afterPropertiesSet() throws Exception {
        List<String> events = Arrays.asList("beforeCreate");
        for (Map.Entry<String, Validator> entry : validators.entrySet()) {
            events.stream()
              .filter(p -> entry.getKey().startsWith(p))
              .findFirst()
              .ifPresent(
                p -> validatingRepositoryEventListener
               .addValidator(p, entry.getValue()));
        }
    }
}

3. Testing

In Section 2.1. we showed that, without a validator, we can add objects without name property into our database which is not desired behavior because we don’t check data integrity.

If we want to add the same object without name property but with provided validator, we will get this error:

curl -i -X POST -H "Content-Type:application/json" -d 
  '{ "email" : "[email protected]" }' http://localhost:8080/users
{  
   "timestamp":1472510818701,
   "status":406,
   "error":"Not Acceptable",
   "exception":"org.springframework.data.rest.core.
    RepositoryConstraintViolationException",
   "message":"Validation failed",
   "path":"/users"
}

As we can see, missing data from request was detected and an object was not saved into the database. Our request was returned with 500 HTTP code and message for an internal error.

The error message doesn’t say anything about the problem in our request. If we want to make it more informational, we will have to modify response object.

In the Exception Handling in Spring article, we showed how to handle exceptions generated by the framework, so that’s definitely a good read at this point.

Since our application generates a RepositoryConstraintViolationException exception we will create a handler for this particular exception which will modify response message.

This is ours RestResponseEntityExceptionHandler class:

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends
  ResponseEntityExceptionHandler {

    @ExceptionHandler({ RepositoryConstraintViolationException.class })
    public ResponseEntity<Object> handleAccessDeniedException(
      Exception ex, WebRequest request) {
          RepositoryConstraintViolationException nevEx = 
            (RepositoryConstraintViolationException) ex;

          String errors = nevEx.getErrors().getAllErrors().stream()
            .map(p -> p.toString()).collect(Collectors.joining("\n"));
          
          return new ResponseEntity<Object>(errors, new HttpHeaders(),
            HttpStatus.PARTIAL_CONTENT);
    }
}

With this custom handler, our return object will have information about all detected errors.

4. Conclusion

In this article, we showed that validators are essential for every Spring Data REST API which provides an extra layer of security for data insertion.

We also illustrated how simple is to create new validator with annotations.

As always, the code for this application can be found in the GitHub project.

I just announced the new Spring Boot 2 material, coming in REST With Spring:

>> CHECK OUT THE LESSONS

newest oldest most voted
Notify of
Jacques Koorts
Guest
Jacques Koorts

Is there some opensource lib one can import with a set of generic validators? I’m doing this, @Numeric(groups = DataType.class) @ApiModelProperty(required = true) private String someField; I had to write my own Numeric @interface and my own NumericValidator class to get to this, @Constraint(validatedBy = {NumericValidator.class}) public @interface Numeric { } With NumericValidator being, public boolean isValid(String value, ConstraintValidatorContext context) { if (numericObj == null) { return false; } if (Strings.isBlank(value) && numericObj.skipValidationIfEmpty()) { return true; } try { Long.parseLong(value); return true; } catch (NumberFormatException | NullPointerException e) { return false; } } Surely someone already wrote a bunch of… Read more »

Ante Pocedulic
Guest
Ante Pocedulic

Hi Jacques,

interesting question indeed. There are few libraries, but I would recommend this libraries since they are solving problems like yours.

First one is Apache Commons Validator (http://commons.apache.org/proper/commons-validator/) which is very powerful library with a lot of features.
Second one is JValidate validation toolkit (http://jvalidate.sourceforge.net/) which doesn’t have so many functionalities as Apache Commons Validator, but still is very powerful and probably it solves your question better then first mentioned library.
To get better view on JValidate, go straight to “User manual” where you can find nice examples how to use JValidate in your application.

Jacques Koorts
Guest
Jacques Koorts

Thanks Ante,

I also saw org.hibernate.validator.constraints for @Email validation, but I will check out these libs. Thanks again.
jk

Jacques Koorts
Guest
Jacques Koorts

I also discovered that using @Pattern you can inline your own Validator with a reg ex. Useful for quick custom validators. Downside is that you can have duplication – regression bugs when using the same reg ex across many end points.

eg. @Pattern(regexp = “BC[0-9]{3}”, message = “{rest.request.validation.field.Format}”)

Tiago Cassio
Guest
Tiago Cassio

Seems configureValidatingRepositoryEventListener method is deprecated in spring data rest 2.5.6, any other way to configure manual validation?

Eugen Paraschiv
Guest

Good point, thanks for the heads up – I’ll let you know when it’s updated.
Cheers,
Eugen.

Max Mumford
Guest
Max Mumford

This was immensely helpful, I had wasted hours trying to figure this out. For those who want exceptions to be rendered as JSON, this might be helpful: @ControllerAdvice public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler({RepositoryConstraintViolationException.class}) public ResponseEntity handleAccessDeniedException(Exception ex, WebRequest request){ // extract field errors RepositoryConstraintViolationException requestException = (RepositoryConstraintViolationException) ex; Map errorMap = new HashMap(); List errors = requestException.getErrors().getFieldErrors(); // iterate over field errors for(FieldError err : errors) { // if “$fieldname.” exists in the error code, remove it String field = err.getField(); String code = err.getCode(); code = code.replace(field + “.”, “”); errorMap.put(field, code); } HttpHeaders responseHeaders = new… Read more »