Hello everyone,

Greetings today!

Today we'll look at how to validate REST API requests with annotations in Spring Boot.

If you're looking for REST API file size validation, check out Spring Boot Rest API - Validate File Size Using Custom Annotation. Also, for REST API file type validation, see Spring Boot Rest API - Validate File Type Using Custom Annotations.

So let's get started.

Annotations that can be used to validate a spring rest API request include

  • @Size
  • @Length
  • @NotBlank
  • @NotNull
  • @NotEmpty 
  • @UniqueElements
  • @Email
  • @Digits
  • @Range
  • @Future
So let's take a closer look at each of them.
  • @Size - @Size is used to validate the length of a string or the absence of a list element.
Example of @Size

@Size(max = 50,message = "{max.title.length.reached}")
private String title;

@Size(max = 5,message = "{max.author.limit.reached}")
private List<String> authors;
  • @Length - @Length is used for the same purpose as @Size, namely validating the length of a string or the number of elements in a list. The main difference between the two is that @Size belongs to the generic javax.validation class, whereas @Length belongs to the hibernate-specific org.hibernate.validator class.
Example of @Length

@Length(max = 50,message = "{max.title.limit.reached}")
private String title;

@Length(max = 5,message = "{max.author.limit.reached}")
private List<String> authors; 
  • @NotBlank - @NotBlank only works with strings to ensure that they are not null and have a length greater than zero.
Example of @NotBlank

@NotBlank(message = "{title.blank}")
private String title;
  • @NotNull - @NotNull is used with string, collections, date, and other wrapper classes such as Long, Integer, Double, and so on to validate that their value is not null, but it can be empty because @NotNull does not validate empty values.
Example of @NotNull

@NotNull(message = "{title.invalid.value}")
private String title;

@NotNull(message = "{price.invalid.value}")
private Float price;
  • @NotEmpty - @NotEmpty is used with string, collections, date, and other wrapper classes such as Long, Integer, Double, and so on, just like @NotNull, but the key difference is that @NotEmpty validates both not null and empty values.
Example of @NotEmpty

@NotEmpty(message = "{author.empty}")
private List<String> authors;

@NotEmpty(message = "{title.invalid.value}")
private String title;
  • @UniqueElements - is used with collections such as list and set to ensure that the elements of the collection are unique.
Example of @UniqueElements

@UniqueElements(message = "{author.not.unique}")
private List<String> authors;
  • @Email -  is used to validate given string is a valid email.
Example of @Email

@Email
private String email;
  • @Digits-  is used in conjunction with float to validate the maximum integer and fraction value supported.
Example of @Digits

@Digits(integer =10,fraction = 4)
private Float value;
  • @Range - is used to specify the minimum and maximum values of Integer, Long, and so on.
Example of @Range 

@Range(min =18,max = 60)
private Integer age;
  • @Future - is used with a date to validate that the date value is only in the future.
Example of @Future 

@Future
private Date expiryDate;

So far, we've seen the most commonly used annotation for validating requests; however, simply adding an annotation isn't enough; we also need some configurations, as shown below.

How to Configure Spring Boot Rest API Validations

To begin, we must add a spring boot starter validation dependency to pom.xml.

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

For validations to work, we must include the @Valid annotation with the @RequestBody.
package com.book.mgt.controller;

import com.book.mgt.dto.BookDTO;
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("/book")
public class BookController {

    @PostMapping("/add")
    public ResponseEntity<String> addBook
    	(@RequestBody @Valid BookDTO bookDTO){
        
        return null;
    }
}
Annotations were used to perform validation within BookDTO and AddressDTO, as shown below.
package com.book.mgt.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.validator.constraints.UniqueElements;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class BookDTO {

    private Long id;

    @NotNull(message = "{price.invalid.value}")
    private Float price;

    @UniqueElements(message = "{author.not.unique}")
    private List<String> authors;

    @NotEmpty(message = "{title.invalid.value}")
    private String title;

    @Valid private AddressDTO publisherAddress;
}
Below is AddressDTO.java class
package com.book.mgt.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class AddressDTO {

    @NotBlank
    @Size(max = 50)
    private String firstLine;

    @NotBlank
    private String city;

    @NotBlank
    private String state;
}
Because AddressDTO is nested within BookDTO, we must use the @Valid annotation within BookDTO where we must declare AddressDTO.

@Valid private AddressDTO publisherAddress;

Now test your code; you will notice that the proper message is not returned in the API response because we have not handled it; instead, you will receive the following response with a 400 status code.


{
    "timestamp": "2021-09-07T14:28:46.503+00:00",
    "status": 400,
    "error": "Bad Request",
    "path": "/book/add"
}

The following step is to handle BindException and return the appropriate error message.
Create a ResponseDTO.java file that will be used to return data in API responses and, in the event of an error, return the appropriate error message to both the user and the front-end developer.

package com.book.mgt.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ResponseDTO {

    private String developerMsg;

    private String userMsg;
}
Then, in the src/main/resources folder, create the error messages.properties file.
We must provide a key-value pair for each error in error messages.properties, where the key is the error key we used with annotation and the value is the actual message we want to return in the response.

Example title.invalid.value=Title is required.

Following that, we must define beans for the message source and configure the error messages.properties file, as shown below.

package com.book.mgt.config;

@Configuration
public class BeanConfiguration {

    @Bean
    public MessageSource messageSource() {
     ReloadableResourceBundleMessageSource messageSource 
      = new ReloadableResourceBundleMessageSource();
     
     messageSource.setBasename("classpath:error_messages");
     messageSource.setDefaultEncoding("UTF-8");
     return messageSource;
    }

    @Bean
    public LocalValidatorFactoryBean validator() {
        LocalValidatorFactoryBean bean 
        	= new LocalValidatorFactoryBean();
        
        bean.setValidationMessageSource(messageSource());
        return bean;
    }
}

The final step is to write an exception handler class to handle the BindException that is thrown when any validation fails. For exception handling, we will use controller advice, and in the exception handler, we will construct a response that will be written as an API response.

  
package com.book.mgt.controlleradvice;

import com.book.mgt.dto.ResponseDTO;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class GlobalExceptionHandler {

    private MessageSource messageSource;

    @ExceptionHandler(BindException.class)
    public ResponseEntity<ResponseDTO> handleBindException
    	(BindException exception){
    
    Map<String,String> errors=new HashMap<>();
           
    exception.getBindingResult().getAllErrors()
    .forEach(error->{
     errors.put(
      ((FieldError)error).getField(),error.getDefaultMessage());
     });

    ResponseDTO responseDTO=new ResponseDTO();
    responseDTO.setDeveloperMsg(errors.toString());
    responseDTO.setUserMsg("Invalid data provided.");
    return new ResponseEntity<>(responseDTO, 
       		HttpStatus.BAD_REQUEST);
    }
}


We have now configured the proper error message as an API response, so when you restart the spring boot application, you will see the below response when the title in the API payload is blank.

{
    "developerMsg": "{title=Title is required.}",
    "userMsg": "Invalid data provided."
}

With the status 400 Bad Request. We can identify which field has an error using the developer message, and we can also separate this and show an error message against a field that has the error using some javascript code.

So far, we've seen how to validate a rest API request with spring boot and annotations.
If you have any questions, please leave a comment.

Thanks
Enjoy your learning!

Another post you can refer to is,


Backlink To Festival Images