In my Spring Boot app, I implemented a global exception handler class as shown below:
#RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#Override
#ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
protected ResponseEntity<Object> handleMethodArgumentNotValid() {
// code omitted for brevity
return ResponseEntity.unprocessableEntity().body(errorResponse);
}
// other type of exceptions
}
And in my Controller, I return ResponseEntity<ApiResponse> as shown below:
#GetMapping("/categories/{id}")
public ResponseEntity<ApiResponse<CategoryResponse>> findById(#PathVariable long id){
final CategoryResponse response = categoryService.findById(id);
return ResponseEntity.ok(
new ApiResponse<>(
Instant.now(clock).toEpochMilli(), Constants.SUCCESS, response));
}
Here is my ApiResponse class:
#Data
#AllArgsConstructor
public class ApiResponse<T> {
private Long timestamp;
private final String message;
private final T data;
public ApiResponse(Long timestamp, String message) {
this.timestamp = timestamp;
this.message = message;
this.data = null;
}
}
My problem is that; when there is an error, I cannot catch it on Controller and GlobalExceptionHandler directly return error in ResponseEntity<Object> type. When I send requests via Postman, I get the error message, but when I implemented a frontend app, I realized that this format is different than return type when there is no error.
So, I think I should manage the exception as well in the Controller and return the same type data to frontend so that the return value can easily be manipulated. How should I solve this problem? I do not want to use try-catch and do not move my business logic to the Controller. Instead, maybe I should change return type of ResponseEntity<Object> in the exception handler to a similar one in the Controller. Or may need to return exception to the Controller. What should I do?
Update: I had already implemented a custom exception class:
public class ElementAlreadyExistsException extends RuntimeException {
public ElementAlreadyExistsException() {
super();
}
public ElementAlreadyExistsException(String message) {
super(message);
}
public ElementAlreadyExistsException(String message, Throwable cause) {
super(message, cause);
}
}
And use it in my `GlobalExceptionHandler` as shown below:
#ExceptionHandler(ElementAlreadyExistsException.class)
#ResponseStatus(HttpStatus.CONFLICT)
public ResponseEntity<Object> handleElementAlreadyExistsException(ElementAlreadyExistsException ex, WebRequest request) {
return buildErrorResponse(ex, HttpStatus.CONFLICT, request);
}
And the errors are build as shown below:
private ResponseEntity<Object> buildErrorResponse(Exception ex,
HttpStatus httpStatus,
WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(httpStatus.value(), message);
return ResponseEntity.status(httpStatus).body(errorResponse);
}
And here is the response class that I use for exception:
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ErrorResponse {
private final int status;
private final String message;
private String stackTrace;
private List<ValidationError> errors;
#Data
private static class ValidationError {
private final String field;
private final String message;
}
public void addValidationError(String field, String message) {
if (Objects.isNull(errors)) {
errors = new ArrayList<>();
}
errors.add(new ValidationError(field, message));
}
}
Related
I have a rest service that receives an object like this:
public class Item implements Serializable{
private static final long serialVersionUID = 1L;
#Id
private String ID;
private String name;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date insertDate;
//getter setter
}
My controller has a saveItem method of this type:
#RequestMapping(value = "/item", method = RequestMethod.POST, produces = "application/json")
public Object saveItem(#RequestBody(required = false) Item item) {
if (item == null) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
//code..
return new ResponseEntity<>(HttpStatus.OK);
}
when an incorrect date format arrives, an error is generated that is not handled by me because it does not enter the if. Why does this happen? How should it be managed?
The problem occurred even without the #JsonFormat annotation
I suppose you are using spring boot. You can use ControllerAdvice in order to define response in case of exceptions. You can define multiple handlers as multiple methods annotated with ExceptionHandler.
#Slf4j
#ControllerAdvice
public class RestControllerAdvice {
#ExceptionHandler(InvalidArgumentException.class)
public ResponseEntity<ResponseDto<Object>> handleException(Exception exception) {
log.error(exception.getMessage(), exception);
return new ResponseEntity<>(<someBody>, HttpStatus. BAD_REQUEST);
}
#ExceptionHandler(Exception.class)
public ResponseEntity<ResponseDto<Object>> handleException2(Exception exception) {
log.error(exception.getMessage(), exception);
return new ResponseEntity<>(<someBody>, HttpStatus. INTERNAL_SERVER_ERROR);
}
}
I have a global exception handler with #RestControllerAdvice and #ExceptionHandler(APIException.class) methods.
I have designed my own response class ValidationResponse.class which I am adding to Response entity class.
I want to respond with ValidationResponse but getting some generic response instead.
Global Exception Handler
#RestControllerAdvice
public class RestResponseExceptionHandler {
#ExceptionHandler(APIException.class)
public ResponseEntity<ValidationResponse> handleException(APIException ex) {
ValidationResponse validationResponse = new ValidationResponse(ex.getErrorCode(), ex.getErrorMessage());
return new ResponseEntity<>(validationResponse, ex.getHttpStatus());
}
}
Custom exception class
#Getter
#Setter
public class APIException extends RuntimeException {
private int errorCode;
private String errorMessage;
private HttpStatus httpStatus;
public APIException(int errorCode, String errorMessage, HttpStatus httpStatus) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.httpStatus = httpStatus;
}
public APIException(int errorCode, String errorMessage, HttpStatus httpStatus, Exception e) {
super(e);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.httpStatus = httpStatus;
}
}
Custom response design
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#JsonInclude(Include.NON_NULL)
public class ValidationResponse {
public int errorCode;
public String errorMessage;
}
Expected response
{
"errorCode": 1010,
"errorMessage": "some custome validation message"
}
Current Response
{
"error-message" : "Request processing failed; nested exception is com.package.exception.APIException",
"error-code" : "GENERAL_ERROR",
"trace-id" : null,
"span-id" : null
}
#ControllerAdvice
public class RestResponseExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(APIException.class)
public ResponseEntity<ValidationResponse> handleException(APIException ex, WebRequest webRequest) {
}
}
Try this
I am working on sample demo application for Exception Handling in Spring Boot.I am trying Exception Handling With #ControllerAdvice.
I would like to handle exception thrown by validator. It handles other exceptions but not MethodArgumentNotValidException.
For more details following are the classes I am working on:
Query.java
#Getter
#Setter
#NoArgsConstructor
#Validated
public class Query implements Serializable{
#Size(min = 7, max = 24, message = "Size must be between 7 and 24")
#Pattern(regexp = "[a-zA-Z0-9 ]+", Invalid characters")
private String number;
#Size(max = 2, message = "Size must be between 0 and 2")
#Pattern(regexp = "[a-zA-Z0-9 ]+", message="Invalid characters")
private String language;
}
ErrorResponse.java
#Setter
#Getter
#NoArgsConstructor
#JsonInclude(JsonInclude.Include.NON_NULL)
#Data
public class ErrorResponse
{
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
private LocalDateTime timestamp;
private HttpStatus status;
private int code;
private String error;
private String exception;
private String message;
private String path;
private List<String> errors;
}
CustomExceptionHandler.java
#SuppressWarnings({"unchecked","rawtypes"})
#ControllerAdvice
#Component("error")
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(NotFoundException.class)
public final ResponseEntity<Object> handleNotFoundError(NotFoundException ex, final HttpServletRequest request) {
ErrorResponse error = new ErrorResponse();
error.setTimestamp(LocalDateTime.now());
error.setMessage(ex.getMessage());
error.setCode(HttpStatus.NOT_FOUND.value());
return new ResponseEntity(error, HttpStatus.NOT_FOUND);
}
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(InternalServerException.class)
public final ResponseEntity<Object> handleInternelServorError(InternalServerException ex, final HttpServletRequest request) {
ErrorResponse error = new ErrorResponse();
error.setTimestamp(LocalDateTime.now());
error.setMessage(ex.getMessage());
error.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
return new ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(ConstraintViolationException.class)
public void constraintViolationException(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.BAD_REQUEST.value());
}
#ResponseStatus(HttpStatus.BAD_REQUEST)
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
List<String> errorList = ex
.getBindingResult()
.getFieldErrors()
.stream()
.map(fieldError -> fieldError.getDefaultMessage())
.collect(Collectors.toList());
ErrorResponse error = new ErrorResponse();
error.setCode(HttpStatus.BAD_REQUEST.value());
return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
}
}
Request
public ResponseEntity<?> getData(HttpServletRequest httpServletRequest,
#Valid #ApiParam(value = "MANDATORY. The number") #PathVariable(value = "number", required = true) final String partNumber,
#Valid #ApiParam(value = "OPTIONAL. The language") #RequestParam(value = "language", required = false) final String languageKey) {
.............
}
I just ran into this issue and here's how I solved it:
#ControllerAdvice
public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// handle validation exception here
}
}
Note: if you have multiple classes that extend ResponseEntityExceptionHandler and are all #ControllerAdvice, you may have some trouble getting this overidden function to be executed. I had to have this overidden in a base class for all my exception handlers in order for it to finally be used. I may, in the future, put all my exception handlers into one class to avoid this.
source:
https://www.youtube.com/watch?v=Q0hwXOeMdUM
You create List<String> errorList but never use it and return just empty ErrorResponse
I would like to handle exception thrown by validator.
I've made exception handler with ControllerAdvice annotation. It handles other exceptions but not MethodArgumentNotValidException.
Exception handler
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value
= {ResourceNotFoundException.class, EntityNotFoundException.class})
protected ResponseEntity<Object> handleNotFound(
RuntimeException ex, WebRequest request) {
APIException apiException = new APIException(HttpStatus.NOT_FOUND,
ex.getMessage(), request);
return handleExceptionInternal(ex, apiException,
new HttpHeaders(), apiException.getStatus(), request);
}
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid
(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
APIException apiException = new APIException(HttpStatus.BAD_REQUEST,
ex.getMessage(), request);
return handleExceptionInternal(ex, apiException,
new HttpHeaders(), apiException.getStatus(), request);
}
}
Validated class (without getters/setters etc.)
public class ClassQuery {
#Min(1)
private Integer minYear;
#Min(1)
private Integer year;
#Min(1)
private Integer maxYear;
private String name;
private String profile;
}
Rest api controller
#GetMapping
public Page<Class> getClasses(#Valid ClassQuery classQuery, Pageable pageable) {
return classService.getClasses(classQuery, pageable);
}
Api Exception (without getters/setters etc.)
public class APIException {
private Date timestamp;
private HttpStatus status;
private String message;
private String path;
public APIExceptionMessage(HttpStatus status, String message, WebRequest request) {
this();
this.status = status;
this.message = message;
this.path = getRequestURI(request);
}
}
Currently I'm getting an empty response with BAD_REQUEST http status from validator while other exceptions are handled correctly. I've also tried no extending ResponseEntityExceptionHandler and handle it with #ExceptionHandler but it was ignoring my response body, in response it gave default error message. I'm not getting any error.
This may be a bit too late.
I had the same problem where MethodArgumentNotValidException was not being handled by the class annotated as ControllerAdvice. In my case, I wanted to serialize and send a custom ErrorDTO Object as JSON to the HTTP Client.
Solution:
MethodArgumentNotValidException should be imported from org.springframework.web.bind.MethodArgumentNotValidException.
I am trying to post a request to my service, but it's not working. I am getting 400 Bad Request. I have GET requests that are working perfectly in the same controller.
Here is the method:
#RequestMapping(value = "/assign", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Form5398Obj arriveTrip(#PathVariable String siteId,
#RequestBody ErrorMsg anError) throws Exception {
System.out.println(anError.toString());
}
The ErrorMessage java class is as follows:
public class ErrorMsg {
private String code;
private String msg;
private String request;
public ErrorMsg(String code, String msg, String request)
{
this.code = code;
this.msg = msg;
this.request = request;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getRequest() {
return request;
}
public void setRequest(String request) {
this.request = request;
}
}
I did not configure anything else. What else do I need to do to get it to work? I am using JavaConfig, do I need to add any bean declarations?
I am sending:
with Content-Type: application/json
{
"code" : "101",
"msg" : "Hello Test",
"request" : "1"
}
I believe you need a no-argument constructor for ErrorMsg so that Jackson can instantiate an object to populate for the incoming request. Otherwise it would not know how the parameters in your 3 parameter constructor should be populated.
Try adding the following
public ErrorMsg() {
}