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
Related
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));
}
}
My RMQ listener:
#RabbitListener(
bindings = #QueueBinding(
value = #Queue,
exchange = #Exchange(
value = "${rabbitmq.exchange.sales-exchange.name}",
type = "${rabbitmq.exchange.sales-exchange.type}",
ignoreDeclarationExceptions = "true"
),
key = "${rabbitmq.exchange.sales-exchange.sales-bk}"
)
)
public void listenSalesOrderCreatedMessage(
#Headers Map<String, Object> headers,
#Payload SalesDTO payload
)
{
log.info("Message Received!");
}
salesDTO:
package org.laptop.sale.event.subscriber.dto;
#JsonInclude(JsonInclude.Include.NON_NULL)
#Getter
#Setter
public class SalesOrderCreatedEventDTO implements Serializable {
#JsonProperty("vendorDetails")
#Valid
private VendorDetails vendorDetails;
#JsonInclude(JsonInclude.Include.NON_NULL)
#Getter
#Setter
public static class VendorDetails implements Serializable {
#JsonProperty("name")
private String name;
#JsonProperty("vendorCommission")
private Double vendorCommission;
}
}
My #Around Aspect to be executed before and after the message call:
package org.laptop.sale.event.subscriber;
#Component
#Aspect
#Slf4j
public class SubscriberAdvice {
#Around("execution(* org.laptop.sale.event.subscriber..*(..)))")
public void payloadValidationFailed(ProceedingJoinPoint joinPoint) {
try {
joinPoint.proceed();
} catch (Throwable throwable) {
log.error("Exception in the process execution {}", throwable);
}
}
}
Invalid Message payload:
{
"vendorDetails": {
"name": "Vendor Name",
"vendorCommission": "wrongData"
}
}
Valid Message payload:
{
"vendorDetails": {
"name": "Vendor Name",
"vendorCommission": 8000.1
}
}
Here the the program flow is entering the aspect only incase of Valid payload. It's not entering the Aspect incase of Invalid Payload. I tried with #AfterThrowing as well. that did not work either
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 need to validate the request msisdn array type, I'm using rest validation of spring boot and how can I add the value in error message too.
I tried custom validation but I cannot find any array validation.
Desired error response
{
"errors": [
"Invalid msisdn: 0917854*****",
"Invalid msisdn: 0936895*****"
],
"success": false,
}
Request Body
{
"msisdn": ["0917854*****", "0936895*****"],
"message": "test message",
"title": "test title"
}
java object
public class PushNotif {
//validate this List
private List<String> msisdn;
#NotNull(message = "Message is required")
private String message;
private String title;
public PushNotif(List<String> msisdn, String message, String title) {
this.msisdn = msisdn;
this.message = message;
this.title = title;
}
}
java controller
#RestController
public class PushController extends BaseController{
#PostMapping(path = "/push")
public ResponseEntity<Object> indexAction(#valid #RequestBody PushNotif pushNotif){
return new ResponseEntity<Object>(null,HttpStatus.ok);
}
}
Error Response Handler
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler
{
#Override
#ResponseStatus(HttpStatus.BAD_REQUEST)
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
Map<String, Object> errors = new HashMap<>();
List<String> details = new ArrayList<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
details.add(error.getDefaultMessage());
});
errors.put("details", details);
errors.put("success", false);
errors.put("traceid", "aksjdhkasjdhs-akjsdjksa-asjkdh");
return new ResponseEntity<>(errors, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
UPDATE: I added Parameter constraints to my java object
#NotNull(message = "msisdn is required")
private List<#NotNull(message = "msisdn is required")
#Pattern(regexp = "^09[0-9]{9}",
message = "Invalid msisdn ${validatedValue}") String> msisdn;
In order to validate values in a list, in this case values not null, you can add the #NotNull annotation in object reference type.
private List<#NotNull String> msisdn;
References
Hibernate Validations for Nested Container Elements
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.