Skip to content

Instantly share code, notes, and snippets.

@rattanchauhan
Last active October 26, 2021 09:20
Show Gist options
  • Save rattanchauhan/78a6607c4cca5ba718154298624b10e3 to your computer and use it in GitHub Desktop.
Save rattanchauhan/78a6607c4cca5ba718154298624b10e3 to your computer and use it in GitHub Desktop.
Error Handling REST Spring Boot
package com.app.exception.common;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import javax.validation.ConstraintViolation;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Data
@AllArgsConstructor
@Builder
public class ApiError {
private ZonedDateTime timestamp;
private int status;
private String exception;
private String message;
private String path;
private String debugMessage;
private List<ApiSubError> subErrors;
private void addSubError(ApiSubError subError) {
if (subErrors == null) {
subErrors = new ArrayList<>();
}
subErrors.add(subError);
}
private void addValidationError(String object, String field, Object rejectedValue, String message) {
addSubError(new ApiValidationError(object, field, rejectedValue, message));
}
private void addValidationError(String object, String message) {
addSubError(new ApiValidationError(object, message));
}
private void addValidationError(FieldError fieldError) {
this.addValidationError(
fieldError.getObjectName(),
fieldError.getField(),
fieldError.getRejectedValue(),
fieldError.getDefaultMessage());
}
public void addValidationErrors(List<FieldError> fieldErrors) {
fieldErrors.forEach(this::addValidationError);
}
private void addValidationError(ObjectError objectError) {
this.addValidationError(
objectError.getObjectName(),
objectError.getDefaultMessage());
}
public void addValidationError(List<ObjectError> globalErrors) {
globalErrors.forEach(this::addValidationError);
}
/**
* Utility method for adding error of ConstraintViolation. Usually when a @Validated validation fails.
* @param cv the ConstraintViolation
*/
private void addValidationError(ConstraintViolation<?> cv) {
this.addValidationError(
cv.getRootBeanClass().getSimpleName(),
((PathImpl) cv.getPropertyPath()).getLeafNode().asString(),
cv.getInvalidValue(),
cv.getMessage());
}
public void addValidationErrors(Set<ConstraintViolation<?>> constraintViolations) {
constraintViolations.forEach(this::addValidationError);
}
abstract class ApiSubError {
}
@Data
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
class ApiValidationError extends ApiSubError {
private String object;
private String field;
private Object rejectedValue;
private String message;
ApiValidationError(String object, String message) {
this.object = object;
this.message = message;
}
}
}
# timestamp toggle for jackson serialization
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS: false
#other properties
server.port=8090server.context-path=/api
management.security.enabled=false
endpoints.shutdown.enabled=true
spring.main.banner-mode=off
package com.app.exception;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import com.app.exception.common.ApiError;
import javax.validation.ConstraintViolationException;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.List;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
/**
* Handle MissingServletRequestParameterException. Triggered when a 'required' request parameter is missing.
*
* @param ex MissingServletRequestParameterException
* @param headers HttpHeaders
* @param status HttpStatus
* @param request WebRequest
* @return the ApiError object
*/
@Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return buildResponseEntity(ex.getParameterName() + " parameter is missing", ex, status, request);
}
/**
* Handle HttpMediaTypeNotSupportedException. This one triggers when JSON is invalid as well.
*
* @param ex HttpMediaTypeNotSupportedException
* @param headers HttpHeaders
* @param status HttpStatus
* @param request WebRequest
* @return the ApiError object
*/
@Override
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex,HttpHeaders headers, HttpStatus status, WebRequest request) {
StringBuilder builder = new StringBuilder();
builder.append(ex.getContentType());
builder.append(" media type is not supported. Supported media types are ");
ex.getSupportedMediaTypes().forEach(t -> builder.append(t).append(", "));
return buildResponseEntity(builder.toString(), ex, HttpStatus.UNSUPPORTED_MEDIA_TYPE, request);
}
/**
* Handle MethodArgumentNotValidException. Triggered when an object fails @Valid validation.
*
* @param ex the MethodArgumentNotValidException that is thrown when @Valid validation fails
* @param headers HttpHeaders
* @param status HttpStatus
* @param request WebRequest
* @return the ApiError object
*/
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return buildResponseEntity("Method Arguments Not Valid Error", ex, BAD_REQUEST, request, ex.getBindingResult().getFieldErrors(), ex.getBindingResult().getGlobalErrors());
}
/**
* Handles javax.validation.ConstraintViolationException. Thrown when @Validated fails.
*
* @param ex the ConstraintViolationException
* @return the ApiError object
*/
@ExceptionHandler(javax.validation.ConstraintViolationException.class)
protected ResponseEntity<Object> handleConstraintViolation(javax.validation.ConstraintViolationException ex, WebRequest request) {
return buildResponseEntity("Validation Error", ex, BAD_REQUEST, request);
}
/**
* Handle HttpMessageNotReadableException. Happens when request JSON is malformed.
*
* @param ex HttpMessageNotReadableException
* @param headers HttpHeaders
* @param status HttpStatus
* @param request WebRequest
* @return the ApiError object
*/
@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return buildResponseEntity("Malformed JSON request", ex, BAD_REQUEST, request);
}
/**
* Handle HttpMessageNotWritableException.
*
* @param ex HttpMessageNotWritableException
* @param headers HttpHeaders
* @param status HttpStatus
* @param request WebRequest
* @return the ApiError object
*/
@Override
protected ResponseEntity<Object> handleHttpMessageNotWritable(HttpMessageNotWritableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return buildResponseEntity("Error writing JSON output", ex, HttpStatus.INTERNAL_SERVER_ERROR, request);
}
/**
* Handle javax.persistence.EntityNotFoundException
*/
@ExceptionHandler(javax.persistence.EntityNotFoundException.class)
protected ResponseEntity<Object> handleEntityNotFound(javax.persistence.EntityNotFoundException ex, WebRequest request) {
return buildResponseEntity("Entity Not Found", ex, HttpStatus.NOT_FOUND, request);
}
/**
* Handle DataIntegrityViolationException, inspects the cause for different DB causes.
*
* @param ex the DataIntegrityViolationException
* @return the ApiError object
*/
@ExceptionHandler(DataIntegrityViolationException.class)
protected ResponseEntity<Object> handleDataIntegrityViolation(DataIntegrityViolationException ex, WebRequest request) {
if (ex.getCause() instanceof ConstraintViolationException) {
return buildResponseEntity("Database error", ex, HttpStatus.CONFLICT, request);
}
return buildResponseEntity("Unexpected error", ex, HttpStatus.INTERNAL_SERVER_ERROR, request);
}
/**
* Handle Exception, handle generic Exception.class
*
* @param ex the Exception
* @return the ApiError object
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
protected ResponseEntity<Object> handleMethodArgumentTypeMismatch(MethodArgumentTypeMismatchException ex, WebRequest request) {
String message = String.format("The parameter '%s' of value '%s' could not be converted to type '%s'", ex.getName(), ex.getValue(), ex.getRequiredType().getSimpleName());
return buildResponseEntity("Missing Arguments", ex, BAD_REQUEST, request);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Object> handleBadRequest(final IllegalArgumentException e, WebRequest request) {
return buildResponseEntity("Missing Arguments", e, BAD_REQUEST, request);
}
private ResponseEntity<Object> buildResponseEntity(String message, Exception ex, HttpStatus status, WebRequest request) {
ServletWebRequest servletWebRequest = (ServletWebRequest) request;
ApiError responseBody = ApiError
.builder()
.timestamp(ZonedDateTime.now())
.exception(ex.getClass().getName())
.path(servletWebRequest.getRequest().getServletPath())
.message(message)
.debugMessage(ex.getLocalizedMessage())
.status(BAD_REQUEST.value())
.build();
return ResponseEntity.status(status).body(responseBody);
}
private ResponseEntity<Object> buildResponseEntity(String message, Exception ex, HttpStatus status, WebRequest request, List<FieldError> fieldErrors, List<ObjectError> globalErrors) {
ServletWebRequest servletWebRequest = (ServletWebRequest) request;
ApiError responseBody = ApiError
.builder()
.timestamp(ZonedDateTime.now())
.exception(ex.getClass().getName())
.path(servletWebRequest.getRequest().getServletPath())
.message(message)
.debugMessage(ex.getLocalizedMessage())
.status(BAD_REQUEST.value())
.build();
responseBody.addValidationErrors(fieldErrors);
responseBody.addValidationError(globalErrors);
return ResponseEntity.status(status).body(responseBody);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment