bean validation custom validator hibernate validator jsr303 Latest spring boot spring rest spring validator

Spring REST Validation Example – Mkyong.com

On this article we’ll improve the earlier spring REST Whats up World example by including bean validation and customized validation.

Methods Used:

  • Spring Boot 2.1.2.RELEASE
  • Spring 5.1.four.RELEASE
  • Maven 3
  • Java eight

1. Driver

Examine the earlier REST driver once more:

BookController.java

package deal com.mkyong;

deliver com.mkyong.error.BookNotFoundException;
deliver com.mkyong.error.BookUnSupportedFieldPatchException;
import org.springframework.beans.manufacturing unit.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.net.bind.annotation. *;

import java.util.Record;
import java.util.Map;

@RestController
public class BookController

@Autowired
a personal BookRepository archive;

// Discovery
@GetMapping ("/ Books")
Listing findAll ()
restore repository.findAll ();

// Save
@PostMapping ("/ books")
@ResponseStatus (HttpStatus.CREATED)
E-book a brand new ebook (@RequestBody E-book newBook)
backupbook.save (newBook);

// Discovery
@GetMapping ("/ books / id")
E-book findOne (@PathVariable Long id)
restore archive.findById (id)
.orElseThrow (() -> new BookNotFoundException (s));

// …

2. Bean Validation (Hibernate Validator)

2.1 Bean Validation is mechanically enabled if any JSR-303 implementation (reminiscent of Hibernate Validator) is out there on class paths. By default, the Spring Boot will get and masses the Hibernate Validator routinely.

2.2 The POST request under is accepted, we must highlight bean validation to make sure that fields akin to identify, writer, and worth usually are not empty.

@PostMapping ("/ books")
E-book a new e-book (@RequestBody Guide newBook)
backupbook.save (newBook);

curl -v -X POST localhost: 8080 / books -H "Content Type: application / json" -d "" identify ":" ABC ""

2.3 Mark the bean with javax.validation.constraints. * Annotations.

E-book.java

package deal com.mkyong;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;

@Entity
public class ebook

@Id
@GeneratedValue
personal Long ID;

@NotEmpty (message = "Give Name")
personal language identify;

@NotEmpty (message = "Enter author")
personal string;

@NotNull (message = "Enter Price")
@DecimalMin ("1.00")
personal BigDecimal worth;

// …

2.4 Add @Legitimate @RequestBody. Prepared, bean validation is now in use.

BookController.java

import javax.validation.Legitimate;

@RestController
public class BookController

@PostMapping ("/ books")
Ebook a new e-book (@Valid @RequestBody Guide newBook)
backupbook.save (newBook);

// …

2.5 Attempt to resend the POST request to the REST endpoint. If bean validation fails, it can launch MethodArgumentNotValidException. By default, spring sends 400 dangerous requests for HTTP standing but no errors.

curl -v -X POST localhost: 8080 / books -H "Content Type: application / json" -d "" identify ":" ABC ""

Word: Pointless use of -X or –request has already been determined.
* Making an attempt: 1 …
* TCP_NODELAY set
* Related to localhost (:: 1) port 8080 (# zero)
> POST / Books HTTP / 1.1
> Host: localhost: 8080
> Consumer Agent: curl / 7.55.1
> Settle for: * / *
> Content material Sort: Software / Json
> Content material Size: 32
>
* The download has been utterly despatched off: 32 to 32 bytes
<HTTP / 1.1 400
<Content material Length: zero
<Date: Wed 20.2.2019 13:02:30 GMT
<Connection: close
<

2.6 The above error response shouldn’t be pleasant, we will get MethodArgumentNotValidException and ignore the corresponding answer:

CustomGlobalExceptionHandler.java

package deal com.mkyong.error;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.net.bind.MethodArgumentNotValidException;
import org.springframework.net.bind.annotation.ControllerAdvice;
import org.springframework.net.context.request.WebRequest;
import org.springframework.net.servlet.mvc.technique.annotation.ResponseEntityExceptionHandler;

import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Record;
import java.util.Map;
import java.util.stream.Collectors;

@ControllerAdvice
public class CustomGlobalExceptionHandler expands ResponseEntityExceptionHandler

// @ Valid error message
@Bypass
protected ResponseEntity handleMethodArgumentNotValid (MethodArgumentNotValidException ex,
HttpHeaders-headers,
HttpStatus Mode, WebRequest Request)

Map physique = new LinkedHashMap <> ();
physique.put ("timestamp", new date ());
physique.put ("status", status.worth ());

// Get all the errors
Record errors = ex.getBindingResult ()
.getFieldErrors ()
.stream ()
.map (x -> x.getDefaultMessage ())
Gather (Collectors.toList ());

body.put ("errors", errors);

return new ResponseEntity <> (frame, headers, standing);

2.7 Attempt once more. Ready.

curl -v -X POST localhost: 8080 / books -H "Content Type: application / json" -d "" identify ":" ABC ""

"Time Stamp": "2019-02-20T13: 21: 27,653 + 0000"
"Status": 400,
"Errors": [
“Please provide a author”,
“Please provide a price”
]

three. Validation of Route Variables

3.1 We will additionally apply javax.validation.constraints. * -Data to the path variable or even the request parameter instantly.

3.2 Spread the @Validated class degree and add javax.validation.constraints. * Annotations for Path Variables:

BookController.java

import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Min;

@RestController
@Validated // class degree
public class BookController

@GetMapping ("/ books / id")
Ebook findOne (@PathVariable @Min (1) Long Id) // jsr 303 annotations
restore archive.findById (id)
.orElseThrow (() -> new BookNotFoundException (s));

// …

three.three The default error message is sweet, only error code 500 is just not appropriate.

curl -v localhost: 8080 / books / 0

"Time Stamp": "2019-02-20T13: 27: 43,638 + 0000"
"Status": 500,
"error": "Internal server error"
"message": "findOne.id: must be greater than or equal to 1",
"Path": "/ Books / 0"

three.four If the @Validated perform fails, it is going to launch ConstraintViolationException, we will bypass this error code:

CustomGlobalExceptionHandler.java

package deal com.mkyong.error;

import org.springframework.http.HttpStatus;
import org.springframework.net.bind.annotation.ControllerAdvice;
import org.springframework.net.bind.annotation.ExceptionHandler;
import org.springframework.net.servlet.mvc.technique.annotation.ResponseEntityExceptionHandler;

import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.IOException;

@ControllerAdvice
public class CustomGlobalExceptionHandler expands ResponseEntityExceptionHandler

@ExceptionHandler (ConstraintViolationException.class)
public void constraintViolationException (HttpServletResponse response) throws IOException
response.sendError (HttpStatus.BAD_REQUEST.worth ());

// ..

curl -v localhost: 8080 / books / 0

"Time Stamp": "2019-02-20T13: 35: 59,808 + 0000"
"Status": 400,
"error": "Poor request"
"message": "findOne.id: must be greater than or equal to 1",
"Path": "/ Books / 0"

4. Custom Validator

four.1 We create a custom validator in the writer area, which allows solely four elements to be stored within the database.

by Writer.java

package deal com.mkyong.error.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Goal;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target (FIELD)
@Retention (REST TIME)
@Constraint (validatedBy = AuthorValidator.class)
@Documented
public @interface Writer

Character Message () default "Author not allowed.";

Class [] teams () default ;

Category [] payload () default ;

AuthorValidator.java

package deal com.mkyong.error.validator;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.Record;

public class AuthorValidator implements ConstraintValidator

Listing by Arrays.asList ("Santideva", "Marie Kondo", "Martin Fowler", "mkyong");

@Bypass
public boolean isValid (String worth, ConstraintValidatorContext context)

restore writers. Consists of (worth);

Ebook.java

package deal com.mkyong;

import com.mkyong.error.validator.Writer;

import javax.persistence.Entity;
import javax.validation.constraints.NotEmpty;
// …

@Entity
public class ebook

@writer
@NotEmpty (message = "Enter author")
personal string;

// …

4.2 Check it. If Custom Validation fails, it can launch methodArgumentNotValidException

curl -v -X POST localhost: 8080 / books
-H "Content Type: Application / Json"
-d "" identify ":" Spring REST tutorials "," writer ":" abc "," worth ":" 9.99 ".

"Time Stamp": "2019-02-20T13: 49: 59,971 + 0000"
"Status": 400,
"Errors": [“Author is not allowed.”]

5. Spring Integration Check

5.1 Check with MockMvc

BookControllerTest.java

package deal com.mkyong;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Check;
import org.junit.runner.RunWith;
import org.springframework.beans.manufacturing unit.annotation.Autowired;
import org.springframework.boot.check.autoconfigure.net.servlet.AutoConfigureMockMvc;
import org.springframework.boot.check.context.SpringBootTest;
import org.springframework.boot.check.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.check.context.ActiveProfiles;
import org.springframework.check.context.junit4.SpringRunner;
import org.springframework.check.net.servlet.MockMvc;

import static org.hamcrest.Matchers. *;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.occasions;
import static org.mockito.Mockito.verify;
import static org.springframework.check.net.servlet.request.MockMvcRequestBuilders.submit;
import static org.springframework.check.net.servlet.outcome.MockMvcResultHandlers.print;
import static org.springframework.check.net.servlet.end result.MockMvcResultMatchers.jsonPath;
import static org.springframework.check.net.servlet.end result.MockMvcResultMatchers.status;

@RunWith (SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles ("test")
public class BookControllerTest

personal static last ObjectMapper om = new ObjectMapper ();

@Autowired
personal MockMvc mockMvc;

@MockBean
personal BookRepository mockRepository;

/ *

"Time stamp": "2019-03-05T09: 34: 13,280 + 0000"
"Status": 400,
"Errors": [“Author is not allowed.”,”Please provide a price”,”Please provide a author”]

* /
@Check
public void save_emptyAuthor_emptyPrice_400 () throws an exception

The string bookInJson = "" identify ":" ABC "";

mockMvc.perform (submit ("/ books")
.content material (bookInJson)
.header (HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
.andDo (print ())
.andExpect (state (). isBadRequest ())
.andExpect (jsonPath ("$. timestamp" is (notNullValue ())))
.and waiting (jsonPath ("$. status" is (400)))
.andExpect (jsonPath ("$. errors). isArray ())
.andExpect (jsonPath ("$. errors", hasSize (3))
.andExpect (jsonPath ("$. errors", hasItem ("Author not allowed."))))
.andExpect (jsonPath ("$. errors", hasItem ("Enter Author")))
.andExpect (jsonPath ("$. errors", hasItem ("Enter Price")));

verify (mockRepository, occasions (0)). save (any (E-book.class));

/ *

"Time Stamp": "2019-03-05T09: 34: 13,207 + 0000"
"Status": 400,
"Errors": [“Author is not allowed.”]

* /
@Check
public void save_invalidAuthor_400 () throws an exception

String bookInJson = "" identify ":" Spring REST tutorials "," writer ":" abc "," worth ":" 9.99 "";

mockMvc.carry out (submit ("/ books")
.content material (bookInJson)
.header (HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
.andDo (print ())
.andExpect (state (). isBadRequest ())
.andExpect (jsonPath ("$. timestamp" is (notNullValue ())))
.and waiting (jsonPath ("$. status" is (400)))
.andExpect (jsonPath ("$. errors). isArray ())
.andExpect (jsonPath ("$. errors", hasSize (1))
.andExpect (jsonPath ("$. errors", hasItem ("Author not allowed.")));

verify (mockRepository, occasions (zero)). save (any (E-book.class));

5.2 Check with TestRestTemplate

BookControllerRestTemplateTest.java

package deal com.mkyong;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
convey org.json.JSONException;
import org.junit.Check;
import org.junit.runner.RunWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.manufacturing unit.annotation.Autowired;
import org.springframework.boot.check.context.SpringBootTest;
import org.springframework.boot.check.mock.mockito.MockBean;
import org.springframework.boot.check.net.shopper.TestRestTemplate;
import org.springframework.http. *;
import org.springframework.check.context.ActiveProfiles;
import org.springframework.check.context.junit4.SpringRunner;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.occasions;
import static org.mockito.Mockito.verify;

@RunWith (SpringRunner.class)
@SpringBootTest (webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // for rest
@ActiveProfiles ("test")
public class BookControllerRestTemplateTest

personal static remaining ObjectMapper om = new ObjectMapper ();

@Autowired
personal TestRestTemplate restTemplate;

@MockBean
personal BookRepository mockRepository;

/ *

"Time Stamp": "2019-03-05T09: 34: 13,280 + 0000"
"Status": 400,
"Errors": [“Author is not allowed.”,”Please provide a price”,”Please provide a author”]

* /
@Check
public void save_emptyAuthor_emptyPrice_400 () throws JSONException

The string bookInJson = "" identify ":" ABC "";

HttpHeaders headers = new HttpHeaders ();
headers.setContentType (MediaType.APPLICATION_JSON);
HttpEntity entity = new HttpEntity <> (bookInJson, headlines);

// ship json with POST
ResponseEntity answer = restTemplate.postForEntity ("/ books", entity, String.class);
// printJSON (answer);

String ExpectedJson = "" Standing ": 400," Errors ": [“Author is not allowed.”,”Please provide a price”,”Please provide a author”]";
assertEquals (HttpStatus.BAD_REQUEST, response.getStatusCode ());
JSONAssert.assertEquals (expectedJson, response.getBody (), false);

verify (mockRepository, occasions (0)). save (any (Ebook.class));

/ *

"Time Stamp": "2019-03-05T09: 34: 13,207 + 0000"
"Status": 400,
"Errors": [“Author is not allowed.”]

* /
@Check
public void save_invalidAuthor_400 () throws JSONException

String bookInJson = "" identify ":" Spring REST tutorials "," writer ":" abc "," worth ":" 9.99 "";

HttpHeaders headers = new HttpHeaders ();
headers.setContentType (MediaType.APPLICATION_JSON);
HttpEntity entity = new HttpEntity <> (bookInJson, headlines);

// Attempt switching
ResponseEntity answer = restTemplate.trade ("/ books", HttpMethod.POST, entity, String.class);

String ExpectedJson = "" Standing ": 400," Errors ": [“Author is not allowed.”]";
assertEquals (HttpStatus.BAD_REQUEST, response.getStatusCode ());
JSONAssert.assertEquals (expectedJson, response.getBody (), false);

verify (mockRepository, occasions (0)). save (any (Guide.class));

personal static clean printJSON (Object object)
String outcome;
attempt
outcomes = om.writerWithDefaultPrettyPrinter (). writeValueAsString (objects);
System.out.println (outcome);
within the hall (JsonProcessingException e)
e.printStackTrace ();

Download Source

References