DTO:
public class User {
@NotNull
private String name;
@NotNull
private String password;
//..
}
Controller:
@RequestMapping(value = "/user", method = RequestMethod.POST)
public ResponseEntity<String> saveUser(@Valid @RequestBody User user) {
//..
return new ResponseEntity<>(HttpStatus.OK);
}
Default json error:
{"timestamp":1417379464584,"status":400,"error":"Bad Request","exception":"org.springframework.web.bind.MethodArgumentNotValidException","message":"Validation failed for argument at index 0 in method: public org.springframework.http.ResponseEntity<demo.User> demo.UserController.saveUser(demo.User), with 2 error(s): [Field error in object 'user' on field 'name': rejected value [null]; codes [NotNull.user.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name]]; default message [may not be null]],"path":"/user"}
I would like to have my custom json for each error occured. How do I accomplish that?
The @Valid annotation ensures the validation of the whole object. Importantly, it performs the validation of the whole object graph. However, this creates issues for scenarios needing only partial validation. On the other hand, we can use @Validated for group validation, including the above partial validation.
The @Valid annotation will tell spring to go and validate the data passed into the controller by checking to see that the integer numberBetweenOneAndTen is between 1 and 10 inclusive because of those min and max annotations.
If you want full control over the response message in every controller write a ControllerAdvice
. For example, that example transform MethodArgumentNotValidException
into a custom json object:
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import java.util.ArrayList;
import java.util.List;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
/**
* Kudos http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/
*
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class MethodArgumentNotValidExceptionHandler {
@ResponseStatus(BAD_REQUEST)
@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
public Error methodArgumentNotValidException(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<org.springframework.validation.FieldError> fieldErrors = result.getFieldErrors();
return processFieldErrors(fieldErrors);
}
private Error processFieldErrors(List<org.springframework.validation.FieldError> fieldErrors) {
Error error = new Error(BAD_REQUEST.value(), "validation error");
for (org.springframework.validation.FieldError fieldError: fieldErrors) {
error.addFieldError(fieldError.getField(), fieldError.getDefaultMessage());
}
return error;
}
static class Error {
private final int status;
private final String message;
private List<FieldError> fieldErrors = new ArrayList<>();
Error(int status, String message) {
this.status = status;
this.message = message;
}
public int getStatus() {
return status;
}
public String getMessage() {
return message;
}
public void addFieldError(String path, String message) {
FieldError error = new FieldError(path, message);
fieldErrors.add(error);
}
public List<FieldError> getFieldErrors() {
return fieldErrors;
}
}
}
You can perform validation with Errors/BindingResult object. Add Errors argument to your controller method and customize the error message when errors found.
Below is the sample example, errors.hasErrors() returns true when validation is failed.
@RequestMapping(value = "/user", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<String> saveUser(@Valid @RequestBody User user, Errors errors) {
if (errors.hasErrors()) {
return new ResponseEntity(new ApiErrors(errors), HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(HttpStatus.OK);
}
I know this is kind of old question,
But I just run into it and I found some pretty good article which has also a perfect example in github.
Basically it uses @ControllerAdvice
as Spring documentation suggests.
So for example catching 400 error will be achieved by overriding one function:
@ControllerAdvice
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(final MethodArgumentNotValidException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
logger.info(ex.getClass().getName());
//
final List<String> errors = new ArrayList<String>();
for (final FieldError error : ex.getBindingResult().getFieldErrors()) {
errors.add(error.getField() + ": " + error.getDefaultMessage());
}
for (final ObjectError error : ex.getBindingResult().getGlobalErrors()) {
errors.add(error.getObjectName() + ": " + error.getDefaultMessage());
}
final ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), errors);
return handleExceptionInternal(ex, apiError, headers, apiError.getStatus(), request);
}
}
(ApiError class is a simple object to hold status, message, errors)
One way to do it is adding message in @NotNull annotation on entity properties. And adding @Valid annotation in controller request body.
DTO:
public class User {
@NotNull(message = "User name cannot be empty")
private String name;
@NotNull(message = "Password cannot be empty")
private String password;
//..
}
Controller:
@RequestMapping(value = "/user", method = RequestMethod.POST)
public ResponseEntity<String> saveUser(@Valid @RequestBody User user) {
//..
return new ResponseEntity<>(HttpStatus.OK);
}
// Add one
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<List<YourErrorResponse>> handleException(MethodArgumentNotValidException ex) {
// Loop through FieldErrors in ex.getBindingResult();
// return *YourErrorReponse* filled using *fieldErrors*
}
@ControllerAdvice(annotations = RestController.class)
public class GlobalExceptionHandler implements ApplicationContextAware {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public ApplicationError validationException(MethodArgumentNotValidException e) {
e.printStackTrace();
return new ApplicationError(SysMessageEnum.MSG_005, e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With