Spring RestController with java validation API. Too verbose error messages - java

I have to write a Rest Controller using Spring.
#PostMapping(value = "/mycontroller", produces = "application/json")
#ResponseBody
#ResponseStatus(HttpStatus.CREATED)
public MyDTOOuptup myMethod(#Valid #RequestBody MyDTO input) {
... body ...
}
I wrote a DTO input object that it represents the Request Body of my controller.
Into the DTO I added some validation rules to validate the input before to analize the request into the controller.
#Data
#AllArgsConstructor
#NoArgsConstructor
public class MyDTO {
#NotNull(message="my custom error message for field_a")
#JsonProperty("field_A")
private String fieldA;
#NotNull(message="my custom error message for field_b")
#JsonProperty("field_B")
private String fieldB;
}
It works fine. In case of wrong input I receive 400 - Bad request and appropriate errors description into the response body.
But, I saw that this json body is too verbose.
{
"timestamp": "2020-03-31T14:29:42.401+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotNull.myDTO.field_a",
"NotNull.field_a",
"NotNull.java.lang.String",
"NotNull"
],
"arguments": [
{
"codes": [
"myDTO.field_a",
"field_a"
],
"arguments": null,
"defaultMessage": "field_a",
"code": "field_a"
}
],
"defaultMessage": "my custom error message for field_a",
"objectName": "myDTO",
"field": "productId",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotNull"
}
],
"message": "Validation failed for object='myDTO'. Error count: 1",
"path": "/mycontroller"
}
How can I specify that I need only the error description messages or something like this?
Is there a smart/slim structure?

You can define your own custom exception and add Exception handler,
class ExceptionResponse {
private boolean success = false;
private String errorCode;
private String errorMessage;
private String exception;
private List<String> errors;
private String path;
private String timestamp = LocalDateTime.now().toString();
}
Exception handler,
#ControllerAdvice
public class CustomExceptionHandler {
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ExceptionResponse> invalidInput(MethodArgumentNotValidException ex, HttpServletRequest request) {
ExceptionResponse response = getExceptionResponse(); //generate exception response
return ResponseEntity.badRequest().contentType(MediaType.APPLICATION_JSON_UTF8).body(response);
}
}
I hope it helps!!

These attributes are available at DefaultErrorAttributes as a Map<String, Object>.
Default implementation of ErrorAttributes. Provides the following attributes when possible: ...
If you want to modify errors you have to first get the map with the attributes and then modify and finally return it. Attributes are get using the DefaultErrorAttributes::getErrorAttributes method. The removal from the map is fairly simple with Map::remove.
Here is the code that should work. The returned bean should be ErrorAttributes.
#Bean
public ErrorAttributes errorAttributes() {
return new DefaultErrorAttributes() {
#Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
errorAttributes.remove("errors");
return errorAttributes;
}
};
}

Related

Swagger/OpenAPI 3.0 generation - adding a new content-type hides previous schema information

I am using Java annotations to build our swagger documentation. In one case, we want to provide output in either JSON or csv depending on the "Accepts" header. I created the rest interface like this:
#RestController
#EnableAutoConfiguration
#RequestMapping(path="/api/v2/swaggerTest")
#Api
public class SwaggerDocResource {
private static class ItemDto {
String name;
Integer age;
}
#ApiOperation(value = "Get the requested items in json")
#GetMapping(produces = "application/json")
public ResponseEntity<ItemDto> getItems() {
return null;
}
#ApiOperation(value = "Get the requested items in csv")
#GetMapping(produces = "text/csv")
public ResponseEntity<String> exportItems() {
return null;
}
}
If I comment out the csv method, the generated Swagger doc generates a schema for my DTO class and references it:
...
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ItemDto"
}
}
}
},
...
However, if I do include the csv method, the schema for my DTO class is no longer generated and both types of response are given the same schema:
...
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "string"
}
},
"text/csv": {
"schema": {
"type": "string"
}
}
}
},
...
Is it possible to assign a different schema to these different content types? I have been unable to figure out how.

Jackson deserialization bellow JSON property

I want to fetchMultiple(ParameterizedTypeReference<List<T>> responseType) for a given List<T>, in this case, I want to get directly a List<Account> but I am getting an error because the list of accounts is encapsulated in another object, as shown below:
{
"accounts": [
{
"accountUid": "c75deb59-5d52-4a23-af7b-fce29927ce9d",
"defaultCategory": "b4189da5-7688-42d0-86e3-14ae9031e01d",
"currency": "GBP",
"createdAt": "2020-08-05T16:50:50.536Z"
}
]
}
There is some Jackson annotation to filter this somehow in order to be processed like this:
[
{
"accountUid": "c75deb59-5d52-4a23-af7b-fce29927ce9d",
"defaultCategory": "b4189da5-7688-42d0-86e3-14ae9031e01d",
"currency": "GBP",
"createdAt": "2020-08-05T16:50:50.536Z"
}
]
POJO
#Data
public class Account {
private String accountUid;
private String defaultCategory;
private String currency;
private String createdAt;
}
RestRequestTemplate.java
public List<T> fetchMultiple(ParameterizedTypeReference<List<T>> responseType) {
return new RestTemplate().exchange(this.url, this.httpMethod, this.request, responseType).getBody();
}
AccountsServiceImpl.java
public List<Account> getAccounts() {
RestRequestTemplate restRequestTemplate = new RestRequestTemplate(GET_ACCOUNTS, HttpMethod.GET, Collections.EMPTY_MAP);
return restRequestTemplate.fetchMultiple(new ParameterizedTypeReference<List<Account>>() {});
}
There is indeed an annotation to ignore the root object. It is called #JsonUnwrapped. Annotate your method with that annotation and your json should be without the root object.

How to modify and bind response to DTO

I am very new to spring boot development.
Currently, I am using #FeignClient to call external API and I want to bind it to my DTO.
My DTO looks like
public class TestDTO {
private UUID uuid;
private String name;
}
My #FeignClient,
#FeignClient(name = "testClient", url = "http://extenalApi/getRules")
public interface DataClient {
#RequestMapping(method = RequestMethod.GET)
List<TestDTO> getRules();
}
It throws an error because external API response is a bit different
{
"data": [
{
"customRule": {
"name": "SAMPLE 1",
"id": "0AB58A47D3A64B56A6B74DA0E66935DD"
}
},
{
"customRule": {
"name": "SAMPLE 2",
"id": "0AE6C846EAF648D1926E665E63633A94"
}
}
}
how can I parse this JSON and make it to
[
{
"name": "SAMPLE 2",
"id": "0AE6C846EAF648D1926E665E63633A94"
},
{ ...
}
]
as my DTO demands.
Seems like you have different structure of API reponse.
Create a new container to hold API response of rules api:
public class RulesDTO {
private List<DataDto> data;
//getter setter
class DataDto{
TestDTO customRule;
//getter setter
}
}
change method to getRules:
#RequestMapping(method = RequestMethod.GET)
RulesDTO getRules();
Now parse it to List of TestDTO for your response:
List<TestDTO> yourData = data.stream().map(DataDto::getCustomRule).collect(Collectors.toList());
Note- It is not working code. Just to give you an idea.

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

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