How to catch `Request processing failed` for client param error in spring boot?

Viewed 869

I register a DateTimeFormatterRegistrar in my class implements WebMvcConfigurer like this:

@Override
public void addFormatters(FormatterRegistry registry) {
    DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
    registrar.setUseIsoFormat(true);
    registrar.registerFormatters(registry);
}

In the rest controller, i try to parse client GET params to an object:

@GetMapping("/api/url/path")
public APIResponse getPersonAttendList(@Valid SampleVO vo){}

SampleVO include field LocalDateTime time. If client offered wrong format of time param, the binding will be failed. Server will return 500, and print some log like this:

>ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] 175 - Servlet.service() for
servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; 
nested exception is java.time.format.DateTimeParseException

My question is, how to catch this exception, and return 400 to the client? It seems ControllerAdvice is not working.

5 Answers

In my project I am using @RestControllerAdvice to handle such cases, an example would be,


@RestControllerAdvice
public class MyCustomExceptionsHandler {
    @ExceptionHandler({HttpMessageNotReadableException.class})
    public ResponseEntity handleException(HttpMessageNotReadableException httpMessageNotReadableException) {
         //return the response entity you need here with the correct error
    }
}

This is something that is working for me.

You should be able to catch this with a "try{}, catch(){}, and finally{}". You would try the code and then catch the exception, then return a 400 error to the client.

try {
    //your code
} catch (java.time.format.DateTimeParseException e) {
    //Return 400 here
} finally {
    //Other code here
}

I'm sorry. I made a mistake here. First, i design to return client all response like

@Getter
@Setter
@AllArgsConstructor
public class APIResponse {
    private Integer status;
    private String msg;
    private Object data;

    @NotNull
    public static APIResponse fromData(Object data) {
        return new APIResponse(0, "", data);
    }

    @NotNull
    public static APIResponse fromError(@NotNull BaseError err) {
        return new APIResponse(err.getStatus(), err.getMsg(), null);
    }

    @NotNull
    public static APIResponse fromError(@NotNull BaseError err, Object data) {
        return new APIResponse(err.getStatus(), err.getMsg(), data);
    }

    @NotNull
    public static APIResponse fromEmpty() {
        return new APIResponse(0, "", null);
    }
}

I made a global error catch like this:

@RestControllerAdvice
@Slf4j
public class ErrorWrapper {

    private String getErrMsg(BindingResult bindingResult) {
        StringBuilder stringBuilder = new StringBuilder();
        for (FieldError error : bindingResult.getFieldErrors()) {
            stringBuilder.append(error.getDefaultMessage()).append(";");
        }
        String msg = stringBuilder.toString();
        log.debug("valid error:{}", msg);
        return msg;
    }

    /**
     * Bean validation error
     * @see javax.validation.Valid
     * @see org.springframework.validation.Validator
     * @see org.springframework.validation.DataBinder
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public APIResponse paramNotValidHandler(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        return APIResponse.fromError(new ParamError(getErrMsg(bindingResult)));
    }

    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public APIResponse paramBindErrorHandler(BindException e) {
        BindingResult bindingResult = e.getBindingResult();
        return APIResponse.fromError(new ParamError(getErrMsg(bindingResult)));
    }


    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    private APIResponse paramConvertErrorHandler(MethodArgumentTypeMismatchException e) {
        log.debug("valid error:", e);
        return APIResponse.fromError(new ParamError("argument error:%s", e.getCause().getMessage()));
    }

    @ExceptionHandler(ServletRequestBindingException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    private APIResponse paramBindErrorHandler(ServletRequestBindingException e) {
        return APIResponse.fromError(new ParamError("param bind error"));
    }


    @ExceptionHandler(InvalidPropertyException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public APIResponse invalidProperty(InvalidPropertyException e) {
        return APIResponse.fromError(new ParamError(e.getPropertyName() + " format error"));
    }

    @ExceptionHandler(PermissionError.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    private APIResponse permissionErrorHandler(PermissionError e) {
        log.debug("not allowed:", e);
        return APIResponse.fromError(e);
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    private APIResponse methodError() {
        return APIResponse.fromError(new ClientError("HTTP Method error"));
    }

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    private APIResponse fileTooLarge() {
        return APIResponse.fromError(new ClientError("file is too big"));
    }

    @ExceptionHandler(MissingServletRequestPartException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    private APIResponse badRequest(MissingServletRequestPartException e) {
        return APIResponse.fromError(new ClientError("file not exist"));
    }

    @ExceptionHandler(HttpMessageConversionException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    private APIResponse badRequest(HttpMessageConversionException e) {
        return APIResponse.fromError(new ClientError("can't parse"));
    }

    @ExceptionHandler(ClientAbortException.class)
    private APIResponse clientAbortHandler(ClientAbortException e) {
        return null;
    }

    @ExceptionHandler(ClientError.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    private APIResponse clientErrorHandler(ClientError e) {
        log.debug("bad request:", e);
        return APIResponse.fromError(e);
    }

    @ExceptionHandler(ServerError.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    private APIResponse serverErrorHandler(ServerError e) {
        log.error("server error:" + e.getMsg(), e);
        return APIResponse.fromError(e);
    }

    @ExceptionHandler(DataIntegrityViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    private APIResponse duplicateKeyError(DataIntegrityViolationException e) {
        log.debug("duplicate source:", e);
        return APIResponse.fromError(new ClientError("db unqiue key error"));
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    private APIResponse unknownErrorHandler(Exception e) {
        String tips = "unknown error:";
        if (e.getCause() != null) {
            tips += e.getCause().getMessage();
        } else if (e.getMessage() != null) {
            tips += e.getMessage();
        } else {
            tips += e.toString();
        }
        log.error(tips, e);
        return APIResponse.fromError(new ServerError());
    }

So, if no catcher matched above, the last one will return 500.

There are so many exceptions for spring boot, i don't know how to catch them all and return 400 without missing any one.

Surround this statement(DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar()) in try catch block .

try{
    DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
} catch (Exception e) {
    e.printStacktrace();
}

If you want to catch all spring's exceptions in your controller advice then you need to catch Exception.class and then check the class name of the exception.

@Slf4j
@RestControllerAdvice(basePackages = "com.your.package.name")
public class ErrorWrapper {

    @ExceptionHandler(Exception.class)
    public APIResponse handleException(Exception ex, WebRequest request) {
        if (ex.getClass().getCanonicalName().startsWith("org.springframework.")) {
            return APIResponse.fromError(new ClientError(ex.getMessage()));
        }
        return APIResponse.fromError(new ServerError());
    }

}
Related