Hello! In this article, I’m gonna cover how to override default exception handler method for MethodArgumentNotValidException
which is thrown when Java Bean Validation Annotations are violated.<!–more–>
What is Java Bean Validation?
Java Bean Validation is a specification that is defined in JSR-380. It makes possible defining constraints with annotations, write custom constraints, etc. I will use Hibernate Validator since it is the only certified implementation of Bean Validation 2.0. It’s not a requirement to use with Spring but I’m going to implement it with Spring Boot because of its popularity. I will not explain how to use these annotations however you can find it in one of my articles.
Create a Simple Project and Provide Some Annotations
Create a project with Spring Initializr and select Web and Lombok dependencies. Because web-starter includes Hibernate Validator. Then create a class named User as below.
@Data
public class User {
@Length(min = 2, max = 30, message = "Name must be between 2-30 characters. ")
@Pattern(regexp = "^[a-zA-Z]+(([',. -][a-zA-Z])?[a-zA-Z]*)*$", message = "Name is invalid.")
private String name;
@Length(min = 2, max = 30, message = "Surname must be between 2-30 characters.")
@Pattern(regexp = "^[a-zA-Z]+(([',. -][a-zA-Z])?[a-zA-Z]*)*$", message = "Surname is invalid.")
private String surname;
@Email(message = "Enter a valid email address.")
private String email;
Using an external message source for error messages is better but I didn’t use it because it would make this article longer.
Then create a RestController and accept User as a @Valid
input.
@RestController
@RequestMapping("/api/users")
public class UserRestController {
@PostMapping
public ResponseEntity<User> saveUser(@Valid @RequestBody User user) {
return ResponseEntity.ok(user);
}
}
Do not forget to use
@Valid
annotation. Because it makes sure that the annotated parameter is validated.
Run the application and send an example request. (You don’t have to use cURL of course. I sent this request with Postman. You can just copy this and import it as a raw text to Postman.)
curl --location --request POST 'http://localhost:8080/api/users' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Marion",
"surname": "Cotillard",
"email": "marion@cotillard.com",
"birthdate": "1975-09-30"
}'
This request will return 200 as could be expected. Now let’s break some rules:
curl --location --request POST 'http://localhost:8080/api/users' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "M",
"surname": "Cotillard",
"email": "marion@cotillard.com",
"birthdate": "1975-09-30"
}'
I just broke @Length
rule on name
field. This request responded with a long response:
{ "timestamp": "2020-02-01T21:27:06.935+0000", "status": 400, "error": "Bad Request", "errors": [ { "codes": [ "Length.user.name", "Length.name", "Length.java.lang.String", "Length" ], "arguments": [ { "codes": [ "user.name", "name" ], "arguments": null, "defaultMessage": "name", "code": "name" }, 30, 2 ], "defaultMessage": "Name must be between 2-30 characters. ", "objectName": "user", "field": "name", "rejectedValue": "M", "bindingFailure": false, "code": "Length" } ], "message": "Validation failed for object='user'. Error count: 1", "path": "/api/users" }
This can be a confusing response but it’s possible to create a custom response.
Override Exception Handler
Firstly, create a POJO for the custom response.
@Getter
@Setter
@Builder
@AllArgsConstructor
public class CustomFieldError {
private String field;
private String message;
}
You can add extra fields but this is enough for this article. Then create an exception handler method:
@ExceptionHandler(MethodArgumentNotValidException.class)
public final ResponseEntity<Object> handleUserMethodFieldErrors(MethodArgumentNotValidException ex, WebRequest request) {
// some logic
}
Javax Annotations throws MethodArgumentNotValidException.class
so, overrode this exception. In the method, extract field errors and create CustomFieldError
objects from them.
final List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
final List<CustomFieldError> customFieldErrors = new ArrayList<>();
for (FieldError fieldError : fieldErrors) {
final String field = fieldError.getField();
final String message = fieldError.getDefaultMessage();
final CustomFieldError customFieldError = CustomFieldError.builder().field(field).message(message).build();
customFieldErrors.add(customFieldError);
}
You can also get rejectedValue
and errorCode
. Then create an HTTP response and return.
return ResponseEntity.badRequest().body(customFieldErrors);
Run the project again and send the same request. This will return 400 response with the body below:
[ { "field": "name", "message": "Name must be between 2-30 characters. " } ]
You can customise this response or override similar exception handlers with the same approach.
Github Repo: https://github.com/kamer/custom-javax-annotation-error-handling
For questions, suggestions or corrections feel free to reach me on:
Email: kamer@kamerelciyar.com
Twitter: https://twitter.com/kamer_ee
暂无评论内容