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.
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 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
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>>(){});