4

I'm using Spring Boot 2.1.5, and I have a class that is mapped to data submitted from a form. The class has an integer field:

class FormData {
   private Integer id;
   ...
}

When I send a payload with the wrong type, e.g:

{
  id: "aaaa"
}

Spring silently returns a 400 status, no exception is thrown, and nothing is printed to the console. I would like to somehow catch this when this happens, so that I would be able to return a proper custom error. I basically use bean validation for all of my other validations, but I can't find a way to apply it for a type mismatch.

Thanks.

Mister_L
  • 2,469
  • 6
  • 30
  • 64

5 Answers5

1

This invalid parameter type conversion throws a TypeMismatchException, that you can handle with a method annotated with @ExceptionHandler, for example, like this :

@ExceptionHandler(TypeMismatchException.class)
@ResponseBody
public MyErrorResponse handleTypeMismatchException(TypeMismatchException typeMismatchException) {
    // create my error response
}

You can place this method inside one controller (in which case it's going to handle only exceptions thrown by this controller), or inside a class annotated with @ControllerAdvice (so it will be generalized to all your controllers).

Gustavo Passini
  • 2,348
  • 19
  • 25
  • 2
    It doesn't work for me - TypeMismatchException is not catched in my ControllerAdvice – Matley Mar 02 '21 at 20:27
  • @Matley Is the ControllerAdvice being picked up by component scanning? Is it targetting a subset of Controllers with options like 'basePackages' or 'annotations'? – Gustavo Passini Mar 02 '21 at 21:28
  • 2
    of course! Are you sure that exception thrown during deserialization (casting String to Long in my case) should be catched in my ControllerAdvice in ExceptionHandler? – Matley Mar 02 '21 at 23:29
1

Spring Controller will throw TypeMismatchException for your case.

To handle any exception at controller level you can annotate the method with @ExceptionHandler

Something like this

@ExceptionHandler(TypeMismatchException.class)
@ResponseBody
public SampleObject handleTypeMismatchException(TypeMismatchException typeMismatchException) {
    ...
}

You can also configure Global Exception Handling With @ControllerAdvice.

@ControllerAdvice
public class GlobalExceptionHandler {

    /** Provides handling for exceptions throughout this service. */
    @ExceptionHandler({ TypeMismatchException.class })
    public final ResponseEntity<ApiError> handleException(Exception ex, WebRequest request) {
        HttpHeaders headers = new HttpHeaders();

        HttpStatus status = HttpStatus.BAD_REQUEST;
        TypeMismatchException tme = (TypeMismatchException) ex;
        return handleTypeMismatchException(tme, headers, status, request);

    }


    /** Customize the response for TypeMismatchException. */
    protected ResponseEntity<ApiError> handleTypeMismatchException(TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        List<String> errorMessages = ex.getErrors()
                .stream()
                .map(contentError -> contentError.getObjectName() + " " + contentError.getDefaultMessage())
                .collect(Collectors.toList());

        return handleExceptionInternal(ex, new ApiError(errorMessages), headers, status, request);
    }

    /** A single place to customize the response body of all Exception types. */
    protected ResponseEntity<ApiError> handleExceptionInternal(Exception ex, ApiError body, HttpHeaders headers, HttpStatus status, WebRequest request) {
        if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
            request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
        }

        return new ResponseEntity<>(body, headers, status);
    }
}
MyTwoCents
  • 7,284
  • 3
  • 24
  • 52
0

Create your own @ControllerAdvice class and handle TypeMismatchException like this

@ExceptionHandler({
        MethodArgumentTypeMismatchException.class,
        TypeMismatchException.class
})
public ResponseEntity<Map<String, String>> handleException(TypeMismatchException e) {
    Map<String, String> errorResponse = new HashMap<>();

    errorResponse.put("message", e.getLocalizedMessage());
    errorResponse.put("status", HttpStatus.BAD_REQUEST.toString());
    return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}

refer to Custom Error Messages in Spring REST API for more

Amit Phaltankar
  • 3,341
  • 2
  • 20
  • 37
0

There can be another error if you are using a complex JSON. In my case, I had something similar with List<BigDecimal> and I had HttpMessageNotReadableException instead of TypeMismatchException

I used a custom validation handler with @RestControllerAdvice (actually you can use @ControllerAdvice as well). What is the difference between these two annotations you can find it here: https://stackoverflow.com/a/43124517/5631885

If you are using extends strategy, like:

@ControllerAdvice    
public class ValidationExceptionHandler extends ResponseEntityExceptionHandler

Just need to override handleHttpMessageNotReadable method

@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(
        HttpMessageNotReadableException ex,
        HttpHeaders headers,
        HttpStatus status,
        WebRequest request) {

    Map<String, Object> body = new LinkedHashMap<>();
    body.put(TIMESTAMP_KEY, new Date());
    body.put(STATUS_KEY, status.value());
    body.put(ERROR_KEY, ex.getCause().getMessage());
    return new ResponseEntity<>(body, headers, status);
}

Otherwise, go with @ExceptionHandler(HttpMessageNotReadableException.class) annotation above the method.

Alex
  • 53
  • 5
-2

Try HttpMessageNotReadableException

Worked for me

Alexander
  • 1
  • 5
  • 1
    Please add further details to expand on your answer, such as working code or documentation citations. – Community Sep 09 '21 at 05:29