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
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));
}
}
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 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.
Im trying to hit Spring REST endpoint in my other module of the application. So im trying to use the REST Template to get a list of users as below :
The API request using REST Template :
public List<LeadUser> getUsersBySignUpType(String type, String id) {
String adminApiUrl = adminApiBaseUrl+"/crm/v1/users/?type="+type+"&id="+id;
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
HttpEntity entity = new HttpEntity(headers);
ResponseEntity<LeadUserList> response = restTemplate.exchange(
adminApiUrl, HttpMethod.GET, entity, LeadUserList.class);
return response.getBody().getUsersList();
}
LeadUserList class :
public class LeadUserList {
private List<LeadUser> usersList;
public List<LeadUser> getUsersList() {
return usersList;
}
}
LeadUser model class :
public class LeadUser {
#JsonProperty("id")
private String id;
#JsonProperty("email")
private String email;
#JsonProperty("name")
private String name;
#JsonProperty("businessName")
private String businessName;
#JsonProperty("phone")
private String phone;
#JsonProperty("address")
private String address;
#JsonProperty("createdTime")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private Date createdTime;
#JsonProperty("updatedTime")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private Date updatedTime;
#JsonProperty("bookletSignups")
private BookletSignUp bookletSignUp;
#JsonProperty("eventSignups")
private EventSignUp eventSignUp;
#JsonProperty("infoSignups")
private InfoSignUp infoSignUp;
#JsonProperty("webinarSignups")
private WebinarSignUp webinarSignUp;
public LeadUser() {
}
}
The API endpoint controller class :
#Controller
#Component
#RequestMapping(path = "/crm/v1")
public class UserController {
#Autowired
UserService userService;
#RequestMapping(value = "/users", method = GET,produces = "application/json")
#ResponseBody
public ResponseEntity<List<User>> getPartnersByDate(#RequestParam("type") String type,
#RequestParam("id") String id) throws ParseException {
List<User> usersList = userService.getUsersByType(type);
return new ResponseEntity<List<User>>(usersList, HttpStatus.OK);
}
}
Although the return type is JSON from the API endpoint im getting the above exception. What have I done wrong here?
The exception :
Could not extract response: no suitable HttpMessageConverter found for response type [class admin.client.domain.LeadUserList] and content type [application/json]
Try additional settings as follows,
httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
Also fix your exchange call,
ResponseEntity<List<LeadUser>> response = restTemplate.exchange(
adminApiUrl, HttpMethod.GET, entity, new ParameterizedTypeReference<List<LeadUser>>(){});