0

I have requirements that if the JSON for the post request is invalid, I will need to send 400 HTTP response codes and if any fields are not parsable, the return status code will be 422. An example post request can be:

    {
        "amount": "12.3343",
        "timestamp": "2018-07-17T09:59:51.312Z"
    }

The Dto class is provided as below,

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TransactionDto {


    @NotNull
    @Min(0)
    private BigDecimal amount;

    @NotNull
    private LocalDateTime timestamp;
}

This is the controller with POST request,

@Slf4j
@RestController
@RequestMapping("/")
@Validated
public class TransactionController {


@Autowired
private TransactionService transactionService;



    @Operation(description = "create a transaction using the provided JSON data")

    @ApiResponses(value = {
        @ApiResponse(responseCode = "201", description = "Create transaction", content = {
            @Content(mediaType = "application/json", schema = @Schema(implementation = Transaction.class))}),
        @ApiResponse(responseCode = "204", description = "Transaction is older than 60 seconds", content = @Content(mediaType = "application/json")),
        @ApiResponse(responseCode = "500", description = MessageConstant.INTERNAL_SERVER_ERROR_MSG, content = @Content)})

    @PostMapping(value = "/transactions")
    public ResponseEntity<Object> createProperty(@RequestBody @Valid TransactionDto transactionDto) {

        try {

            final LocalDateTime transactionTimestamp = transactionDto.getTimestamp();
            final LocalDateTime localDateTimeNow = LocalDateTime.now(ZoneOffset.UTC);

            final long secondsDuration = Duration.between(transactionTimestamp, localDateTimeNow).toSeconds();

            final boolean isFuture = transactionTimestamp.isAfter(localDateTimeNow);

            if (isFuture) {

                return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.UNPROCESSABLE_ENTITY,
                    "Transaction is in future"), new HttpHeaders(), HttpStatus.ACCEPTED);
            }

            if (secondsDuration > 60) {

                return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.NO_CONTENT,
                    "Transaction is older than 60 seconds"), new HttpHeaders(), HttpStatus.NO_CONTENT);
            }

            final Transaction transaction = transactionService.createTransaction(transactionDto);

            return new ResponseEntity<>(transaction, new HttpHeaders(), HttpStatus.CREATED);

        } catch (Exception ex) {

            log.error(MessageConstant.INTERNAL_SERVER_ERROR_MSG + ex.getMessage());
            return new ResponseEntity<>(ApiResponseMessage.getInternalServerError(), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

If the "amount" is, say, "sfdfd", this is not BigDecimal, we should provide the 422. But if the "amount" is "-12.3343", this is a constraint validation error but the data is valid and parsable. So we can't have the 422.

This is my exception handling class,

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {


    @Override
    @Nonnull
    protected ResponseEntity<Object> handleMissingServletRequestParameter(
        MissingServletRequestParameterException ex,
        @Nonnull HttpHeaders headers,
        @Nonnull HttpStatus status,
        @Nonnull WebRequest request
    ) {
        String error = ex.getParameterName() + " parameter is missing";

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.BAD_REQUEST,
            error), new HttpHeaders(), HttpStatus.BAD_REQUEST);
    }

    @Override
    @Nonnull
    protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(
        @Nonnull HttpMediaTypeNotSupportedException ex,
        @Nonnull HttpHeaders headers,
        @Nonnull HttpStatus status,
        @Nonnull WebRequest request
    ) {

        String message = prepareMessageFromException(ex, (ServletWebRequest) request);

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.UNSUPPORTED_MEDIA_TYPE,
            " media type is not supported. Supported media types "), new HttpHeaders(), HttpStatus.UNSUPPORTED_MEDIA_TYPE);
    }

    @Override
    @NonNull
    protected ResponseEntity<Object> handleMethodArgumentNotValid(
        @NonNull MethodArgumentNotValidException ex,
        @NonNull HttpHeaders headers,
        @NonNull HttpStatus status,
        @NonNull WebRequest request

    ) {

        String message = prepareMessageFromException(ex, (ServletWebRequest) request);

        ApiErrorResponse apiError = new ApiErrorResponse(BAD_REQUEST);
        apiError.setMessage("Validation error");
        apiError.addValidationErrors(ex.getBindingResult().getFieldErrors());
        apiError.addValidationError(ex.getBindingResult().getGlobalErrors());
        return buildResponseEntity(apiError);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    protected ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex) {

        ApiErrorResponse apiError = new ApiErrorResponse(BAD_REQUEST);
        apiError.setMessage("Validation error");
        apiError.addValidationErrors(ex.getConstraintViolations());

        return buildResponseEntity(apiError);
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {

        ServletWebRequest req = (ServletWebRequest) request;
        String message = prepareMessageFromException(ex, (ServletWebRequest) request);

        log.info("{} to {}", req.getHttpMethod(), req.getRequest().getServletPath());
        log.error(message, ex);

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.BAD_REQUEST,
            message), new HttpHeaders(), HttpStatus.BAD_REQUEST);
    }

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

        String message = prepareMessageFromException(ex, (ServletWebRequest) request);

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, BAD_REQUEST,
            message), new HttpHeaders(), HttpStatus.BAD_REQUEST);
    }

    private String prepareMessageFromException(Exception ex, ServletWebRequest request) {

        String message = ex.getMessage();

        log.info("{} to {}", request.getHttpMethod(), request.getRequest().getServletPath());
        log.error(message);

        if (message != null && !message.isEmpty()) {
            message = message.split(":")[0];
        }

        return message;
    }

    @Override
    @Nonnull
    protected ResponseEntity<Object> handleHttpMessageNotWritable(
        @Nonnull HttpMessageNotWritableException ex,
        @Nonnull HttpHeaders headers,
        @Nonnull HttpStatus status,
        @Nonnull WebRequest request
    ) {

        String error = "Error writing JSON output";

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.INTERNAL_SERVER_ERROR,
            "Internal server error. please contact support !!"), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @Override
    @Nonnull
    protected ResponseEntity<Object> handleNoHandlerFoundException(
        NoHandlerFoundException ex,
        @Nonnull HttpHeaders headers,
        @Nonnull HttpStatus status,
        @Nonnull WebRequest request
    ) {

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.BAD_REQUEST,
            String.format("Could not find the %s method for URL %s", ex.getHttpMethod(), ex.getRequestURL())), new HttpHeaders(), HttpStatus.BAD_REQUEST);
    }

    private ResponseEntity<Object> buildResponseEntity(ApiErrorResponse apiError) {
        return new ResponseEntity<>(apiError, apiError.getStatus());
    }

    @ExceptionHandler(EntityNotFoundException.class)
    protected ResponseEntity<Object> handleEntityNotFound(EntityNotFoundException ex) {

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.NOT_FOUND,
            "Resource not found: "), new HttpHeaders(), HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(DataIntegrityViolationException.class)
    protected ResponseEntity<Object> handleDataIntegrityViolation(DataIntegrityViolationException ex, WebRequest request) {

        if (ex.getCause() instanceof ConstraintViolationException) {
            return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.CONFLICT,
                "Database error"), new HttpHeaders(), HttpStatus.CONFLICT);
        }

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.INTERNAL_SERVER_ERROR,
            "Internal server error. please contact support !!" + ex.getMessage()), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    protected ResponseEntity<Object> handleMethodArgumentTypeMismatch(MethodArgumentTypeMismatchException ex, WebRequest request) {

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.BAD_REQUEST,
            String.format("The parameter '%s' of value '%s' could not be converted to type '%s'", ex.getName(), ex.getValue(), ex.getRequiredType())), new HttpHeaders(), HttpStatus.BAD_REQUEST);
    }
}

At the moment, I get 400 in both of the cases mentioned. How do I refactor the code to get the correct response code?

11
  • HttpStatus.BAD_REQUEST is going to be 400 I believe Commented Apr 15, 2021 at 2:58
  • Yes, but do I modify the code to reflect the status code based on the requirement? Commented Apr 15, 2021 at 3:04
  • Just try it, see what happens. Commented Apr 15, 2021 at 3:15
  • @TrevorKropp This changes the response code for both of the cases. I will need it 400 and 422 for 2 separate cases. Commented Apr 15, 2021 at 3:50
  • Constraint violation is one of the exceptions. What is the other case? Commented Apr 15, 2021 at 3:52

1 Answer 1

1

I manage to achieve that after some modification of the existing code,

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

        String message = prepareMessageFromException(ex, (ServletWebRequest) request);
        final Throwable throwableCause = ex.getCause();

        if (throwableCause instanceof InvalidFormatException) {

            return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.UNPROCESSABLE_ENTITY,
                message), new HttpHeaders(), HttpStatus.UNPROCESSABLE_ENTITY);
        }

        return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.BAD_REQUEST,
            message), new HttpHeaders(), HttpStatus.BAD_REQUEST);
    }

So when the throwable is InvalidFormatException, I will return the 422 response status code and other the method works as before.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.