Last active
October 26, 2021 09:20
-
-
Save rattanchauhan/78a6607c4cca5ba718154298624b10e3 to your computer and use it in GitHub Desktop.
Error Handling REST Spring Boot
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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