How do I customize default error message from spring @Valid validation?

JsonSpring

Json Problem Overview


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?

Json Solutions


Solution 1 - Json

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;
        }
    }
}

Solution 2 - Json

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);
}

Solution 3 - Json

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)

Solution 4 - Json

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*
}

Solution 5 - Json

@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());

    }

}

Solution 6 - Json

You can do something like this

@ExceptionHandler(value = MethodArgumentNotValidException.class)
      protected ResponseEntity<Error> handleGlobalExceptions(MethodArgumentNotValidException ex,
          WebRequest request) {
        log.catching(ex);
        return new ResponseEntity<>(createErrorResp(HttpStatus.BAD_REQUEST,
            ex.getBindingResult().getFieldErrors().stream().map(err -> err.getDefaultMessage())
                .collect(java.util.stream.Collectors.joining(", "))),
            HttpStatus.BAD_REQUEST);
      }

Solution 7 - Json

For customized the error message in JSON format then do the below steps.

- Create one @Component called CommonErrorHandler

@Component
public class CommonErrorHandler {
public  Map<String,Object> getFieldErrorResponse(BindingResult result){
		
		Map<String, Object> fielderror = new HashMap<>();
		List<FieldError>errors= result.getFieldErrors();
		for (FieldError error : errors) {
			fielderror.put(error.getField(), error.getDefaultMessage());
		}return fielderror;
	}
	
	 public ResponseEntity<Object> fieldErrorResponse(String message,Object fieldError){
		Map<String, Object> map = new HashMap<>();
		map.put("isSuccess", false);
		map.put("data", null);
		map.put("status", HttpStatus.BAD_REQUEST);
		map.put("message", message);
		map.put("timeStamp", DateUtils.getSysDate());
		map.put("filedError", fieldError);
		return new ResponseEntity<Object>(map,HttpStatus.BAD_REQUEST);
	}
}

-- Add InvalidException class

public class InvalidDataException extends RuntimeException {

/**
 * @author Ashok Parmar
 */
	private static final long serialVersionUID = -4164793146536667139L;
	
	private BindingResult result;

	public InvalidDataException(BindingResult result) {
		super();
		this.setResult(result);
	}

	public BindingResult getResult() {
		return result;
	}

	public void setResult(BindingResult result) {
		this.result = result;
	}

}

- Introduce @ControllerAdvice class

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

@ExceptionHandler(InvalidDataException.class)
	public ResponseEntity<?> invalidDataException(InvalidDataException ex, WebRequest request) {

		List<FieldError> errors = ex.getResult().getFieldErrors();
		for (FieldError error : errors) {
			logger.error("Filed Name ::: " + error.getField() + "Error Message :::" + error.getDefaultMessage());
		}
		return commonErrorHandler.fieldErrorResponse("Error", commonErrorHandler.getFieldErrorResponse(ex.getResult()));
	}
	}

-- Use in controller with @Valid and throw exception

public AnyBeans update(**@Valid** @RequestBody AnyBeans anyBeans ,
			BindingResult result) {
		AnyBeans resultStr = null;
		if (result.hasErrors()) {
			**throw new InvalidDataException(result);**
		} else {
				resultStr = anyBeansService.(anyBeans );
				return resultStr;
		}
	}

-- Output will be in JSON format

{
  "timeStamp": 1590500231932,
  "data": null,
  "message": "Error",
  "isSuccess": false,
  "status": "BAD_REQUEST",
  "filedError": {
    "name": "Name is mandatory"
  }
}

Hope this will be work. :-D

Solution 8 - Json

@ControllerAdvice
@RestController
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
       
	    @Override
	    protected ResponseEntity<Object> handleMethodArgumentNotValid(
				MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
	    	  
           // ex.getBindingResult(): extract the bind result for default message. 
              String errorResult = ex.getBindingResult().toString();
	    	 CustomizedExceptionHandlerResponse exceptionResponse = new CustomizedExceptionHandlerResponse(
	    			errorResult, new Date(), request.getDescription(false));
	    	
	    	return new ResponseEntity<>(exceptionResponse, HttpStatus.BAD_REQUEST);
		}

	
}

class CustomizedExceptionHandlerResponse {

   private String message;
   private String status;
   private Date timestamp;

   // constuctor, setters, getters...
}

Solution 9 - Json

Add some information too. If you use just @Valid, you need to catch BindException. If you use @Valid @RequestBody catch MethodArgumentNotValidException

Some sources:
HandlerMethodArgumentResolverComposite.getArgumentResolver(MethodParameter parameter):129 - search which HandlerMethodArgumentResolver support such parameter RequestResponseBodyMethodProcessor.supportsParameter(MethodParameter parameter) - return true if parameter has annotation @RequestBody

RequestResponseBodyMethodProcessor:139 - throw MethodArgumentNotValidException ModelAttributeMethodProcessor:164 - throw BindException

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDisp HayView Question on Stackoverflow
Solution 1 - JsonksokolView Answer on Stackoverflow
Solution 2 - JsonkswaughsView Answer on Stackoverflow
Solution 3 - JsonMatan LachmishView Answer on Stackoverflow
Solution 4 - JsonFoolishView Answer on Stackoverflow
Solution 5 - JsontongtianxiaoView Answer on Stackoverflow
Solution 6 - JsonBala BhaskarView Answer on Stackoverflow
Solution 7 - JsonAshok ParmarView Answer on Stackoverflow
Solution 8 - JsonAkshay MisalView Answer on Stackoverflow
Solution 9 - JsonAntonView Answer on Stackoverflow