How to wrap Path Not Found Exception in Spring Boot Rest using ExceptionHandler? - spring

I am using Spring Boot and Spring Rest Example. In this example, I am passing custom header, if that value is valid, endpoint gets called successfully, if custom header value is not correct then I get below response, which I want to wrap into show it to the enduser using #ControllerAdvice ExceptionHandler.
Note: I went through Spring mvc - How to map all wrong request mapping to a single method, but here in my case I am taking decision based on CustomHeader.
{
"timestamp": "2020-01-28T13:47:16.201+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/employee-data/employee-codes"
}
Controller
#Operation(summary = "Find Employee")
#ApiResponses(value = { #ApiResponse(code = 200, message = "SUCCESS"),
#ApiResponse(code = 500, message = "Internal Server Error") })
#Parameter(in = ParameterIn.HEADER, description = "X-Accept-Version", name = "X-Accept-Version",
content = #Content(schema = #Schema(type = "string", defaultValue = "v1",
allowableValues = {HeaderConst.V1}, implementation = Country.class)))
#GetMapping(value = "/employees/employee-codes", headers = "X-Accept-Version=v1")
public ResponseEntity<Employees> findEmployees(
#RequestParam(required = false) String employeeCd,
#RequestParam(required = false) String firstName,
#RequestParam(required = false) Integer lastName) {
Employees response = employeeService.getEmployees(employeeCd, firstName, lastName);
return new ResponseEntity<>(response, HttpStatus.OK);
}
I've implemented HttpMessageNotReadableException and HttpMediaTypeNotSupportedException and NoHandlerFoundException, but still not able to wrap this error.
Any suggestions?

I was able to find the solution for it.
# Whether a "NoHandlerFoundException" should be thrown if no Handler was found to process a request.
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
Error Handling Code:
#Override
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
// custom logic here
return handleExceptionInternal(ex, error, getHeaders(), HttpStatus.BAD_REQUEST, request);
}

If you're using #ControllerAdvice,
do this:
#ControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
#ExceptionHandler(value
= { IllegalArgumentException.class, IllegalStateException.class })
protected ResponseEntity<Object> handleConflict(
RuntimeException ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
return handleExceptionInternal(ex, bodyOfResponse,
new HttpHeaders(), HttpStatus.CONFLICT, request);
}
}

Related

How I can localized error messages for Spring Validation errors, like #PathVariable #Min(1), #Email etc, if I use #ControllerAdvice?

For example:
#GetMapping("/exception/{id}")
public ResponseEntity getException(#PathVariable #Min(1) Integer id) {
return ResponseEntity.ok().body(id.toString());
}
it must returns "must be greater than or equal to 1" (default message) when I call this function with id=-5 from the eng page. And something like this "doit être supérieur ou égal à 1", when i coll it from french page.
hibernate-validator jar file contains different localization messages. You need to configure based on Accept-Language header in the request it will decide which message to be displayed.
#Bean
public AcceptHeaderLocaleResolver localeResolver() {
final AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
resolver.setDefaultLocale(Locale.US);
return resolver;
}
The Response DTO object.
#Data
public class ErrorDto {
private LocalDateTime timestamp;
private HttpStatus status;
private String error;
public ErrorDto(HttpStatus status, String error) {
timestamp = LocalDateTime.now();
this.status = status;
this.error = error;
}
}
Then in ControllerAdvice catch ConstraintViolationException and show a response.
#ResponseStatus(code = HttpStatus.BAD_REQUEST)
#ExceptionHandler({ConstraintViolationException.class})
public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
String error = "";
for (ConstraintViolation<?> violation : ex.getConstraintViolations()) {
error = violation.getMessage();
}
ErrorDto errorDto = new ErrorDto(HttpStatus.BAD_REQUEST, error);
return new ResponseEntity<>(errorDto, errorDto.getStatus());
}
It will give you response for given controller.
{
"timestamp": "2022-10-23T21:08:07.0459025",
"status": "BAD_REQUEST",
"error": "doit être supérieur ou égal à 1"
}

Mandatory Validation for pathVariable not working

Currently we are adding mandatory validation for pathVariable - having customized message in response.
When #PathVariable is removed, then it is giving 404 error and not going into any of the exceptionHandler. Can you please help to resolve this issue?
#PostMapping(path = "{id}")
public ResponseEntity<String> migrate(#PathVariable(value = "id")
#Size(min = 1, max = 64, message = INVALID_FIELD_LENGTH_DETAILS)
String id) {
...
}
Error response as below:
{
"timestamp": "2022-02-08T15:26:58.649+00:00",
"status": 404,
"error": "Not Found",
"message": "",
"path": "/migrations"
}
javax.validation annotations are not supported yet: https://github.com/spring-projects/spring-framework/issues/11041
You can try several workarounds, e.g.:
#PostMapping(path = {"", "/{id}"})
public ResponseEntity<String> migrate(#PathVariable(value = "id", required = false)
Optional<String> id) {
if (!id.isPresent() && id.get().length() > 64) {
return new ResponseEntity<>("Validation error", HttpStatus.BAD_REQUEST);
} else {
return new ResponseEntity<>(id.orElse(""), HttpStatus.OK);
}
}

How can I hide #ApiResponse form #ControllerAdvice for an endpoint?

I'm trying to migrate our manually writen OpenAPI (swagger) to a generated OpenAPI using springdoc-openapi for our Spring-Boot application. We got some issues, because the controller responses (mostly ErrorCodes) didn't match to the documentatation.
We already used a #ControllerAdvice annotated handler configuration. Here a snippet:
#ControllerAdvice
public class ExceptionHandler {
#ResponseStatus(code = HttpStatus.NOT_FOUND)
#ApiResponse(responseCode = "404", description = "(NOT FOUND) Resource does not exist!", content = #Content)
#ExceptionHandler(NotFoundException.class)
public void handleException(NotFoundException e) {
log.warn("Returning {} due to a NotFoundException: {}", HttpStatus.NOT_FOUND, e.toString());
}
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ApiResponse(responseCode = "400", description = "(BAD REQUEST) Given resource is invalid!", content = #Content)
#ExceptionHandler(InvalidResourceException.class)
public void handleException(InvalidResourceExceptione) {
log.error("Invalid resource: {}", e.toString());
}
The generated API now showed all defined ApiResponses as responses for all controllers and endpoints. So I splittet the handler config using #ControllerAdvice(basePackageClasses = MyController.class) to group the possible exceptions. But there are still responses that are not fitting to all endpoints of a controller. Like:
#RestController
public class MyController {
#ResponseStatus(HttpStatus.CREATED)
#Operation(summary = "Create", description = "Create myResource!")
#PostMapping(value = "/myResources/", produces = {"application/json"})
#ResponseBody
public Integer create(#RequestBody MyResource newResource) throws InvalidResourceException {
return creationService.createResource(newResource).getId();
}
#ResponseStatus(HttpStatus.OK)
#Operation(summary = "Update", description = "Update myResource!")
#PutMapping(value = "/myResources/{id}", produces = {"application/json"})
public void update(#PathVariable("id") Integer id, #RequestBody MyResource newResource)
throws ResourceNotFoundException, InvalidResourceException {
return updateService.updateResource(id, newResource);
}
#ResponseStatus(HttpStatus.OK)
#Operation(summary = "Get", description = "Get myResource!")
#GetMapping(value = "/myResources/{id}", produces = {"application/json"})
#ResponseBody
public MyResource get(#PathVariable("id") Integer id) throws ResourceNotFoundException {
return loadingService.getResource(id);
}
}
POST will never respond with my 'business' 404 and GET will never respond with my 'business' 400. Is it possible to annotate an endpoint, so that not possible response codes are hidden in the API?
I tried to override the responses, but didn't work as intended:
#ResponseStatus(HttpStatus.OK)
#Operation(summary = "Get", description = "Get myResource!")
#ApiResponses({#ApiResponse(responseCode = "200", description = "(OK) Returning myResource"),
#ApiResponse(responseCode = "404", description = "(NOT FOUND) Resource does not exist!")})
#GetMapping(value = "/myResources/{id}", produces = {"application/json"})
#ResponseBody
public MyResource get(#PathVariable("id") Integer id) throws ResourceNotFoundException {
return loadingService.getResource(id);
}
400 still shows up...
You need to remove the #ApiResponse from your #ControllerAdvice class and need to add the respective response in your controller class, as mentioned by you.
#ResponseStatus(HttpStatus.OK)
#Operation(summary = "Get", description = "Get myResource!")
#ApiResponses({#ApiResponse(responseCode = "200", description = "(OK) Returning myResource"),
#ApiResponse(responseCode = "404", description = "(NOT FOUND) Resource does not exist!")})
#GetMapping(value = "/myResources/{id}", produces = {"application/json"})
#ResponseBody
public MyResource get(#PathVariable("id") Integer id) throws ResourceNotFoundException {
return loadingService.getResource(id);
}

Spring Boot #Valid doesn't display message from #NotBlank

Why message from #NotBlank is not displayed?
Controller API:
#PostMapping("/create-folder")
public SuccessResponse createFolder(Principal principal, #Valid #RequestBody CreateFolderRequest request) {
return historyService.createFolder(principal.getName(), request.getFolderName());
}
Request Body:
#Data
public class CreateFolderRequest {
#NotBlank(message = "Folder name is mandatory.")
private String folderName;
}
JSON Response:
{
"timestamp": "2020-11-18T11:24:19.769+00:00",
"status": 400,
"error": "Bad Request",
"message": "Validation failed for object='createFolderRequest'. Error count: 1",
"path": "/api/history/create-folder"
}
Packages:
Valid:
import javax.validation.Valid;
NotBlank:
import javax.validation.constraints.NotBlank;
There is no global exception handlers in the project.
#Valid throw an exception of MethodArgumentNotValidException you massege in #NotBlank is get throw inside exception detail which isn't return to the customer. you need to extract the messages so try adding this method to the controller.
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return errors;
}
above code read all errors inside the exception then get thier detail (filedName - errorMessage) and put them in list and then return the list to the clinet with 400 bad request status

I am not getting error message when validation fails in spring boot hibernate validation

I have created a simple Spring Boot application and I want to apply hibernate validation. But I don't get errors message. I get only below message if validation fails
{
"timestamp": "2020-06-11T09:51:55.695+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/users/"
}
my domain code is below
#NotNull
#Size(min = 8, max = 16)
private String password;
my controller
#PostMapping(consumes = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE }, produces = {
MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<UserRest> createUser(#Valid #RequestBody UserDetailsRequestModel userDetails) {
UserRest userRest = new UserRest();
userRest.setFirstName(userDetails.getFirstName());
userRest.setLastName(userDetails.getLastName());
userRest.setEmail(userDetails.getEmail());
return new ResponseEntity<UserRest>(userRest, HttpStatus.OK);
}

Resources