Spring Boot Remove exception attribute from error responses - java

I have some exception in order to handling errors in my restful spring backend, Here is example of an exception:
#ResponseStatus(value = HttpStatus.FORBIDDEN)
public class IllegalUserAgentException extends RuntimeException
{
public IllegalUserAgentException(String exception)
{
super(exception);
}
}
When i throw this exception from service (in domain driven architecture), Spring returning below json error
{
"timestamp": 1552127820802,
"status": 403,
"error": "Forbidden",
"exception": "com.example.exception.IllegalUserAgentException",
"message": "test",
"path": "/path/somePath"
}
As you can see, Spring added an attribute with "exception" name, I want to remove this attribute.
I added server.error.include-exception=false flag, but not work.
Any solution?

In spring-boot you can do it via defining a #Component which extends DefaultErrorAttributes, then override the getErrorAttributes function to remove the "exception".
#Component
public class CustomErrorAttributes extends DefaultErrorAttributes {
#Override
public Map<String, Object> getErrorAttributes(
WebRequest webRequest,
boolean includeStackTrace
) {
Map<String, Object> errorAttributes
= super.getErrorAttributes(webRequest, includeStackTrace);
errorAttributes.remove("exception");
return errorAttributes;
}
}
There is also another alternatives such as using #ControllerAdvice which you can peruse further.

I would suggest you to make a Response Class.
Add try catch in your service class and return this object instead.
In catch section instantiate ResponseDto object and add the message accordingly.This will gracefully handle your exception. It is scalable because you can wrap any other exception message as well.
public class ResponseDto {
#JsonProperty("url")
private String url;
#JsonProperty("status")
private int status;
#JsonProperty("userMsg")
private String UserMsg;
}

Related

Spring Boot get JSON Error representation in the ErrorController

Spring Boot provides errors in the json format like this:
{
"timestamp": "2019-01-17T16:12:45.977+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Error processing the request!",
"path": "/endpoint"
}
Is it possible for me to obtain this error inside the ErrorController and to proceed with it?
#Controller
public class CustomErrorController implements ErrorController {
#RequestMapping("/error")
public String handleError(Model model) {
// how to get json-error here?
model.addAttribute("resultJson", ?);
return "error";
}
}
Is it inside the HttpServletResponse or maybe something else?
The default error attributes are extracted from a WebRequest using the ErrorAttributes bean.
Spring already provided a default implementation of this bean, the DefaultErrorAttributes bean.
You can have this bean injected by the container, as any usual bean, to your custom /error controller implementation so you can use it:
#Controller
#RequestMapping({"/error"})
public class CustomErrorController extends AbstractErrorController {
public CustomErrorController(final ErrorAttributes errorAttributes) {
super(errorAttributes, Collections.emptyList());
}
#RequestMapping
public String handleError(Model model, HttpServletRequest request) {
Map<String, Object> errorAttributes = this.getErrorAttributes(request, false); // retrieve the default error attributes as a key/value map (timestamp, status, error...)
// ...
}
#Override
public String getErrorPath() {
return "/error";
}
}
Update (by #Igorz)
As of Spring Boot version 2.3.0
org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController#getErrorAttributes(HttpServletRequest, boolean, boolean, boolean)
has been deprecated in favor of:
org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController#getErrorAttributes(HttpServletRequest, ErrorAttributeOptions)
Static factory ErrorAttributeOptions.defaults() can be used as default ErrorAttributeOptions argument of above method.

Spring Controller - show JSON instead of Whitelabel Error Page (in browser)

Hi have a simple spring controller that returns ResponseStatusException in case of error.
#RestController
#RequestMapping("/process")
public class ProcessWorkitemController {
#Autowired MyService service;
#GetMapping("/{id}")
public ResponseEntity<?> findById(#PathVariable Long id) {
Optional<MyObj> opt = service.getObj(id);
opt.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, String.format("Process id %s not found", id), null));
return ResponseEntity.ok(opt.get());
}
}
This works perfctely if invoked with SoapUI: it shows me a Json as follows:
{
"timestamp": "2021-01-20T10:59:48.082+0000",
"status": 404,
"error": "Not Found",
"message": "Process id 1 not found",
"path": "/workspace-manager/process/1"
}
When I call the same service with the browser it shows the whitelabel error page with my custmo error message.
I want a JSON as response even on the browser, is this possible?
Thanks in advance.
I would avoid using ResponseStatusException, especially when building an application that needs a unified way of handling exceptions. What I would do instead is the following:
Create a new class and annotate with an #RestControllerAdvice. This will effectively be the main entrypoint for exception handling of your app.
Create methods that handle different types of exceptions and return a response using the message and/or information from the exceptions.
Simply throw such an exception either from the service layer or from the controller layer and let the exception handler handle it.
A simple example is the following (disregard the inner classes -- only there in order to save space).
Exception handler:
#RestControllerAdvice
public class MyExceptionHandler {
#ExceptionHandler(ServiceException.class)
public ResponseEntity<ExceptionResponse> handleException(ServiceException e) {
return ResponseEntity
.status(e.getStatus())
.body(new ExceptionResponse(e.getMessage(), e.getStatus().value()));
}
public static class ExceptionResponse {
private final String message;
public String getMessage() { return message; }
private final Integer code;
public Integer getCode() { return code; }
public ExceptionResponse(String message, Integer code) {
this.message = message;
this.code = code;
}
}
}
Base ServiceException:
package com.ariskourt.test.controllers;
import org.springframework.http.HttpStatus;
public abstract class ServiceException extends RuntimeException{
public ServiceException(String message) {
super(message);
}
public abstract HttpStatus getStatus();
}
Controller
#RestController
#RequestMapping("/process")
public class ProcessController {
#GetMapping("/{id}")
public ResponseEntity<?> findById(#PathVariable Long id) {
return Optional
.of(1L)
.filter(number -> number.equals(id))
.map(ResponseEntity::ok)
.orElseThrow(() -> new ProcessNotFoundException(String.format("Process with id %s not found", id)));
}
}
With all that in place, you should a unified way of handling exceptions which also always returns the correct message not matter the client.

#NotNull : validation custom message not displaying

I am using spring data JPA for creating application. In that I am trying to implement server side validation using annotation. I added #NotNull annotation on filed with custom message. I also added #valid with #RequestBody
But problem is that when I am passing nAccountId as null I am not getting custom message i.e. Account id can not be null I am getting "message": "Validation failed for object='accountMaintenanceSave'. Error count: 1",.
Can any one please tell me why I am not getting custom message?
Controller code
#PutMapping("/updateAccountData")
public ResponseEntity<Object> saveData(#Valid #RequestBody AccountMaintenanceSave saveObj){
return accService.saveData(saveObj);
}
AccountMaintenanceSave class
import javax.validation.constraints.NotNull;
public class AccountMaintenanceSave {
#NotNull(message="Account id can not be null")
public Integer nAccountId;
#NotNull
public String sClientAcctId;
#NotNull
public String sAcctDesc;
#NotNull
public String sLocation;
#NotNull
public Integer nDeptId;
#NotNull
public Integer nAccountCPCMappingid;
#NotNull
public Integer nInvestigatorId;
//Getter and Setter
}
RestExceptionHandler class
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleAllExceptionMethod(Exception ex, WebRequest requset) {
ExceptionMessage exceptionMessageObj = new ExceptionMessage();
exceptionMessageObj.setMessage(ex.getLocalizedMessage());
exceptionMessageObj.setError(ex.getClass().getCanonicalName());
exceptionMessageObj.setPath(((ServletWebRequest) requset).getRequest().getServletPath());
// return exceptionMessageObj;
return new ResponseEntity<>(exceptionMessageObj, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
I don't know what exactly happen previously and not getting proper message. Now using same code getting result like this with proper message
{
"message": "Validation failed for argument at index 0 in method: public org.springframework.http.ResponseEntity<java.lang.Object> com.spacestudy.controller.AccountController.saveData(com.spacestudy.model.AccountMaintenanceSave), with 1 error(s): [Field error in object 'accountMaintenanceSave' on field 'nAccountId': rejected value [null]; codes [NotNull.accountMaintenanceSave.nAccountId,NotNull.nAccountId,NotNull.java.lang.Integer,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [accountMaintenanceSave.nAccountId,nAccountId]; arguments []; default message [nAccountId]]; default message [Account id can not be null]] ",
"error": "org.springframework.web.bind.MethodArgumentNotValidException",
"path": "/spacestudy/rockefeller/admin/account/updateAccountData"
}
In message filed can I print only [Account id can not be null]?
Try this.
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleAllExceptionMethod(Exception ex, WebRequest requset) {
ExceptionMessage exceptionMessageObj = new ExceptionMessage();
// Handle All Field Validation Errors
if(ex instanceof MethodArgumentNotValidException) {
StringBuilder sb = new StringBuilder();
List<FieldError> fieldErrors = ((MethodArgumentNotValidException) ex).getBindingResult().getFieldErrors();
for(FieldError fieldError: fieldErrors){
sb.append(fieldError.getDefaultMessage());
sb.append(";");
}
exceptionMessageObj.setMessage(sb.toString());
}else{
exceptionMessageObj.setMessage(ex.getLocalizedMessage());
}
exceptionMessageObj.setError(ex.getClass().getCanonicalName());
exceptionMessageObj.setPath(((ServletWebRequest) requset).getRequest().getServletPath());
// return exceptionMessageObj;
return new ResponseEntity<>(exceptionMessageObj, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
It's not so good to make your only ExceptionHandler to catch Exception.class make it ConstraintViolationException.class
Another approach to the solution:
I would suggest remove the exception handler for this validation fields in the POJO and rather let Spring handle the error response by adding the below property into application.properties. By adding this the message configured in the #notnull or any validation annotation can be captured and showed in the response by default and no explicit handling of this validation case is required.
server.error.include-message=always
server.error.include-binding-errors=always

Customizing JAX-RS response when a ConstraintViolationException is thrown by Bean Validation

Bean Validation is a good option to validate objects, but how to customize the response of a REST API (using RESTeasy) when a ConstraintViolationException is thrown?
For example:
#POST
#Path("company")
#Consumes("application/json")
public void saveCompany(#Valid Company company) {
...
}
A request with invalid data will return a HTTP 400 status code with the following body:
[PARAMETER]
[saveCompany.arg0.name]
[{company.name.size}]
[a]
It's nice but not enough, I would like to normalize these kind of errors in a JSON document.
How can I customize this behavior?
With JAX-RS can define an ExceptionMapper to handle ConstraintViolationExceptions.
From the ConstraintViolationException, you can get a set of ConstraintViolation, that exposes the constraint violation context, then map the details you need to an abitrary class and return in the response:
#Provider
public class ConstraintViolationExceptionMapper
implements ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException exception) {
List<ValidationError> errors = exception.getConstraintViolations().stream()
.map(this::toValidationError)
.collect(Collectors.toList());
return Response.status(Response.Status.BAD_REQUEST).entity(errors)
.type(MediaType.APPLICATION_JSON).build();
}
private ValidationError toValidationError(ConstraintViolation constraintViolation) {
ValidationError error = new ValidationError();
error.setPath(constraintViolation.getPropertyPath().toString());
error.setMessage(constraintViolation.getMessage());
return error;
}
}
public class ValidationError {
private String path;
private String message;
// Getters and setters
}
If you use Jackson for JSON parsing, you may want to have a look at this answer, showing how to get the value of the actual JSON property.

Spring jackson deserialization and exception handling

I'm building a REST api in Spring and I have problems with my exception handling. I want to validate the full request and give information about the payload in one go.
Suppose my object is
public class StubJson {
private BigDecimal bigDecimalField;
#NotEmpty
private String stringField;
public void setBigDecimalField(BigDecimal bigDecimalField) { this.bigDecimalField = bigDecimalField; }
public String setStringField(String stringField) { this.stringField = stringField; }
}
And my controller is
#RestController
public class StubController {
#RequestMapping(value = "/stub", method = POST)
public void stub(#Valid #RequestBody StubJson stubJson) {}
}
The validation on this object is in a #ControllerAdvice that translates FieldError objects into translated errors for the end user.
#ResponseStatus(BAD_REQUEST)
#ResponseBody
#ExceptionHandler(value = MethodArgumentNotValidException.class)
public List<ErrorJson> processValidationError(MethodArgumentNotValidException ex) {}
If I pass in this json
{"bigDecimalField": "text", "stringField": ""}
I want a response like this
[
{
"field": "stringField",
"message": "Cannot be empty."
},
{
"field": "bigDecimalField",
"message": "Not a number."
}
]
If I do this I get a
com.fasterxml.jackson.databind.exc.InvalidFormatException
on the BigDecimalField which only contains information about only one field. The only option I see is passing it in as a String or writing custom validation annotations. Is there an easier way of achieving this?
You can use controller advice for this purpose. Declare a controller advice in your application, catch you expected exception, transform to you required response and return. Just remember controller advice will be applied to all of your controller.
#ControllerAdvice
public class ExceptionHandlerController {
#ExceptionHandler(InvalidFormatException.class)
#ResponseBody public String typeMismatchException(HttpServletRequest request, HttpServletResponse servletResponse, InvalidFormatException e ) {
String yourResponse = "";
return yourResponse;
}
}

Categories

Resources