Spring Boot get JSON Error representation in the ErrorController - java

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.

Related

Spring Boot Remove exception attribute from error responses

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;
}

Get original mapping value inside Spring controller method

Since I'm using the CQRS pattern, I'm trying to create a single controller method that accepts every POST call with a command in its request body and send it.
I'm almost there, but I can't get the path variables.
I created a custom HandlerMapping
#Bean
public HandlerMapping requestMappingHandlerMapping() throws NoSuchMethodException {
for (final UrlEnum urlEnumItem : UrlEnum.values()) {
requestMappingHandlerMapping.registerMapping(new RequestMappingInfo(urlEnumItem.getCommandName(),
new PatternsRequestCondition(urlEnumItem.getUrl()),
null,
null,
null,
null,
null,
null),
commandController,
commandController.getClass().getDeclaredMethod("commandHandler", HttpServletRequest.class)
);
}
return requestMappingHandlerMapping;
}
and this is my controller method signature
#RequestMapping(method = {RequestMethod.POST, RequestMethod.PUT}, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Object> commandHandler(final HttpServletRequest request) throws Exception {
// controller code here
}
If the url path is something like /api/test it works, but with something like /api/test/{idEntity} I don't have any PathVariable available in the request.
I tried everything like
String originalUrl = (String) request.getAttribute(
HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
which returns the valued url (i.e. /api/test/1234), not the template, or adding
#PathVariable Map<String, Object> parameters
as a parameter in the method, which is empty.
Debugging the request object it seems there isn't anything useful to identify the path variables.
Maybe I should interrogate the HandlerMapping, but I can't have access to it in the controller method.
Is there a way to extract the pathVariables in the controller method?
It was an error in the configuration. I shouldn't have added the RequestMapping annotation to the controller method because it overrode my configuration.
Now I have
#RestController
public class CommandController extends AbstractController {
private final MappingJackson2JsonView mappingJackson2JsonView = new MappingJackson2JsonView();
#Override
protected ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
// controller code here
return new ModelAndView(mappingJackson2JsonView);
}
}

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;
}
}

Spring REStful web service #initbinder not allowing other validation

I have the following Rest controller:
#RestController
public class DocumentSearchController_global
{
#InitBinder//("TestCustomAnotation")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new ChekAtleastOneValueValidator());
}
#RequestMapping(value = "/validator", method = RequestMethod.POST, produces = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE })
protected DocumentSearchResponse validatortest(#Valid #RequestBody TestCustomAnotation objDMSRequest, Errors e, BindingResult br) throws AppException
{
if(br.hasErrors())
System.out.println("ERRor");
if (e.hasErrors())
{
System.out.println("Got Error: "+ e.getFieldError());
}
DocumentSearchResponse objDocSearchResponse = null;
return objDocSearchResponse;
}
#ExceptionHandler
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ResponseBody
public String handleMethodArgumentNotValidException(
MethodArgumentNotValidException error) {
System.out.println("ERROR-->>>>>>>>>>>>>>>>>>>>>>>>" +error.getMessage());
return "Bad request: " + error.getMessage();
}
}
And this is the bean where the request will be cast:
public class TestCustomAnotation
{
#ValidDocumentModifiedDate({"7Days", "30Days","60Days"})
String docModifiedDate;
#NotNull
String objectId;
#NotNull
String jobId;
Setter and GEtter
}
In the controller if I specify binder.setValidator(new
ChekAtleastOneValueValidator()); the contol will only go to
ChekAtleastOneValueValidator it will not check for #notnull
#ValidDocumentModifiedDate`
If I don't have binder.setValidator(new
ChekAtleastOneValueValidator()); then the control will check for
#notnull#ValidDocumentModifiedDate validation but not
ChekAtleastOneValueValidator.
My question is: is there a way in Spring to use Spring validation, custom annotation and #notnull annotation and get all the error of all the validation or spring allows to use only Spring validators?
Actually the question itself was wrong. I got the answer I use a Spring Validator class to validate all the request comming in and then use #validated in stead of #valid. I don't use annotation at the request anymore and let the class be a POJO. thats it problem solved

Categories

Resources