Handle exceptions as spring do [duplicate] - java

This question already has answers here:
Spring3 #ExceptionHandler for ServletRequestBindingException
(4 answers)
Closed 4 years ago.
I would like to handle exceptions without override default spring handler for validation.
If i do not implement an exception handler by #ControllerAdvice, my validation erros will response like this:
{
"timestamp": "2018-09-25T13:15:30.037+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"Null.notificationEntity.id",
"Null.id",
"Null.java.math.BigInteger",
"Null"
],
"arguments": [
{
"codes": [
"notificationEntity.id",
"id"
],
"arguments": null,
"defaultMessage": "id",
"code": "id"
}
],
"defaultMessage": "must be null",
"objectName": "notificationEntity",
"field": "id",
"rejectedValue": 15,
"bindingFailure": false,
"code": "Null"
}
],
"message": "Validation failed for object='notificationEntity'. Error count: 1",
"path": "/v1/notifications"
}
The repsonse above is nice and clear for me, but if i create an exception handler, to handle my own exceptions, i have to manually handle validation errors throwed by #Valid.
I have found here a sample about it: http://www.springboottutorial.com/spring-boot-validation-for-rest-services
But on this example i still have to implement manually, and my result is:
{
"timestamp": "2018-09-25T13:07:22.779+0000",
"status": 400,
"code": "BAD_REQUEST",
"error": "Bad Request",
"message": "org.springframework.validation.BeanPropertyBindingResult: 1 errors\nField error in object 'notificationEntity' on field 'id': rejected value [15]; codes [Null.notificationEntity.id,Null.id,Null.java.math.BigInteger,Null]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [notificationEntity.id,id]; arguments []; default message [id]]; default message [must be null]",
"path": "/v1/notifications"
}
My question is, how could i implement my own exception handler without override spring validation handler or how could i call spring validation handler to keep the same response for validation exceptions?

If you go with catching the exception in your ControllerAdvice, you will have access to the validation exception (which I assume is the one with the structure you want to replicate).
#ExceptionHandler(ValidationException.class)
#ResponseBody
public ResponseEntity<YourCustomResponse> handleValidationexception(ValidationException e) {
// probably do some logging
YourCustomResponse response = buildCustomResponse(e); // build the response payload however you see fit
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
In this way, you can set the body, the HTTP headers and the HTTP status that is returned to the client in case of a ValidationException.

Related

how to get custom error code through swagger annotation

*`I have below schema definition to represent commission amount in my openapi contract.`*
"properties": {
"Amount": {
"type": "number",
"description": "The amount of tnx",
"pattern" : "^-?[0-9]*(\\.([0-9]{1,2}))?$",
"format": "big-decimal",
"minimum": 0.01,
"maximum": 49999.99,
"exclusiveMinimum": false,
"exclusiveMaximum": false,
"example" : "9999.99"
},
generated code:
#NotNull #Valid #DecimalMin("0.01") #DecimalMax("49999.99")
#Schema(name = "amount", example = "9999.99", description = "The amount of tnx", required = true)
public BigDecimal getAmount() {
return amount;
}
when i gave above 50000 or below 0.01 its given the right validation message through post man
{
"errors": [
{
"code": "DecimalMax",
"message": "must be less than or equal to 49999.99",
"field": "Amount"
}
]
}
{
"errors": [
{
"code": "DecimalMin",
"message": "must be greater than or equal to 0.01",
"field": "Amount"
}
]
}
but i want override the code with some some number to represent the error code like 100,101 etc
{
"errors": [
{
"code": "100",
"message": "must be less than or equal to 49999.99",
"field": "Amount"
}
]
}
{
"errors": [
{
"code": "101",
"message": "must be greater than or equal to 0.01",
"field": "Amount"
}
]
}
can i specify any annotation on swagger to achieve this
By Default, when a field fails validation, Spring will throw a MethodArgumentNotValidException. This is managed internally by Springs spring-boot-starter-validation. You can override this exception handling using #RestControllerAdvice or #ControllerAdvice.
First, create a basic model for your response. Here is an extremely simple one.
#Setter
#Getter
public class RestException {
String code;
String message;
}
Then, create your controller advice class.
#RestControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
RestException exception = new RestException();
exception.setCode(HttpStatus.BAD_REQUEST.toString());
var message = Optional.ofNullable(ex).map(MethodArgumentNotValidException::getBindingResult).map(BindingResult::getFieldError).orElse(null);
exception.setMessage(Objects.isNull(message) ? "unidentified error" : message.getField() + " " + message.getDefaultMessage());
return new ResponseEntity<>(exception, HttpStatus.BAD_REQUEST);
}
}
this very simple example will return a response of
{
"code": "400 BAD_REQUEST",
"message": "amount must be greater than or equal to 0"
}
Note
You are free to set the code returned to the user as whatever you want (aka, exception.setCode("99999 MY_STATUS_CODE"). However, this will have no meaning to the user, as it won't match the headers sent by the controller as part of the ResponseEntity. I recommend sticking with defined HttpStatus types as the code you display back to your users.
Also, unless you are specifically trying to get a BigDecimal as your type, setting the format of the number to float or double will result in the expected dataType. Otherwise, declaring big-decimal isn't necessary, since that is the default for a number with no format defined.
Likewise, the defaults for exclusiveMinimum and exclusiveMaximum is false by default, so there is no need to include them in your schema.
Finally, I recommend adjusting your schema to include to response description. Something along the lines of this
responses:
"200":
description: OK
"5XX":
description: Unknown error has occurred
"400":
description: A required parameter is missing or an invalid datatype or value was provided for property.
Check the error message to check the invalid information, adjust the provided schema, and try again.
That should cover all of your bases. Feel free to expand the RestException and RestExceptionHandler classes. As I said, these are basic examples. You can make them much more robust (i.e. multiple returns based upon the field or error message).

Unable to launch a Dataflow template using API client library (Java) as I am getting Invalid argument exception

The interesting fact is that I am able to launch the Dataflow template from cloud shell, as well as from Google OAuth2 playground console. When I try to launch it using the API client library with the same set of details, I am getting 400 Bad request error.
URL Used:
GenericUrl URL = new GenericUrl("https://dataflow.googleapis.com/v1b3/projects/servicetesting-g-1575763471750/templates:launch?gcsPath=gs://dataflow-templates/latest/GCS_Text_to_Cloud_PubSub");
POST Body content:
{
"jobName":"st-txt-to-pubsub",
"parameters":
{
"inputFilePattern":"gs://soa_global_storage/input/my_input.txt",
"outputTopic":"projects/servicetesting-g-1575763471750/topics/Input_Topic_PUB"
},
"environment": { "zone": "us-central1" }
}
Exception:
Exception in thread "main" com.google.api.client.http.HttpResponseException: 400 Bad Request
{
"error": {
"code": 400,
"message": "The template parameters are invalid.",
"errors": [
{
"message": "The template parameters are invalid.",
"domain": "global",
"reason": "badRequest"
}
],
"status": "INVALID_ARGUMENT"
}
}
Take a look on your template metadata, be sure the field name is the same you call in the POST body. Check also if the parameters pass on the regex test.
As others have said, you also have to correct your zone to us-central1-a, or you can leave it blank.
Ex. of metadata that would fit your content:
{
"description": "enter your description",
"name": "template_name",
"parameters": [
{
"name": "inputFilePattern",
"helpText": "Bucket URI",
"regexes":["^gs://.*"],
"label": "input file pattern"
},
{
"name": "outputTopic",
"helpText": "lorem ipsum",
"label": "Topic on Pub/sub",
}
]
}
Worth to mention that you should try the template with the parameters in the console. If doesn't work there, it won't work anywhere.

How to throw Spring #Valid "exception"?

I'm using Spring to build my REST api. One of my methods is decorated with #Valid. This produces a JSON that looks like this:
{
"timestamp": "2019-11-04T19:07:08.387+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotNull.customer.firstName",
"NotNull.firstName",
"NotNull.java.lang.String",
"NotNull"
],
"arguments": [
{
"codes": [
"customer.firstName",
"firstName"
],
"arguments": null,
"defaultMessage": "firstName",
"code": "firstName"
}
],
"defaultMessage": "must not be null",
"objectName": "customer",
"field": "firstName",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotNull"
}
],
"message": "Validation failed for object='customer'. Error count: 1",
"path": "/customers"
}
Doing a Google search, to produce a HTTP 400 error, I'm supposed to throw ResponseStatusException(HttpStatus.BAD_REQUEST);
This produces a JSON like this:
{
"timestamp": "2019-11-04T19:08:02.408+0000",
"status": 400,
"error": "Bad Request",
"message": "400 BAD_REQUEST",
"path": "/customers"
}
The message is easy to change obviously, but I'm more asking about how to get the Errors array. That doesn't seem to be in ResponseStatusException?
I can recreate it pretty easily, but I was hoping there was one built already?
EDIT: This isn't input validation per say... this is throwing an error FOR the input, but I need to hit the database first to do the actual validation, so that part is in my service layer and throws an internal exception if it isn't valid.
If you are using the Validator, then you can customize the errors using a configuration file like validator.properties

Proper way to parse the body of an error with Spring's RestTemplate

I'm using the #Valid annotation on the #RequestBody argument which uses Hibernate Validation, to ensure it's validated:
public ResponseEntity<?> register(#RequestBody #Valid RegistrationModel registration) { ...
Following the recommendations of the Spring Rest book, I'm catching that exception and turning it into a nice JSON error message that looks like this:
{
"title": "Validation Failed",
"status": 400,
"detail": "Input validation failed",
"timeStamp": 1505345551757,
"developerMessage": "org.springframework.web.bind.MethodArgumentNotValidException",
"errors": {
"hashedPassword": [
{
"code": "NotNull",
"message": "must not be null"
}
],
"organization": [
{
"code": "NotBlank",
"message": "must not be blank"
}
],
"name": [
{
"code": "NotBlank",
"message": "must not be blank"
}
]
}
}
On the client side, I'm doing this to make the call:
Object result = restTemplate.postForObject("http://localhost:8080/v1/registration", registration, Object.class);
Using Object until I define exactly on the representation of the response. When there's a validation error though, I get a 400 error which throws a HttpClientErrorException.
HttpClientErrorException has a getResponseBodyAsString() that responds with the JSON I pasted above. I could manually parse it, but I was wondering what's the most correct way to parse it. RestTemplate clearly has a mechanism for automatically parsing responses when there's no errors. Does it have something for errors?

Documentation of Spring Validation Errors

Spring, als always, provides very useful defaults to handle Validation errors. But sometimes it looks difficult to customize those. In my case I have a custom validation that uses a javascript function to validate a field in a domain object. The default validation error produces 4 message codes that use the object name, the field name, the field type and the validation type. So far so good. But I would like to add an additional code that contains the name of the js-function as a component. How could I do that?
Or more general my question is: where do I find a documentation of the way Spring builds the default error messages, and how they can be manipulated.
In my case I get an output like:
{
"timestamp": 1457092927829,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.MethodArgumentNotValidException",
"errors": [
{
"codes": [
"JSValidated.order.validFrom",
"JSValidated.validFrom",
"JSValidated.java.time.ZonedDateTime",
"JSValidated"
],
"arguments": [
{
"codes": [
"order.validFrom",
"validFrom"
],
"arguments": null,
"defaultMessage": "validFrom",
"code": "validFrom"
},
"checkOrder",
"static/OrderValidator.js"
],
"defaultMessage": "validation checkValidFrom failed",
"objectName": "order",
"field": "validFrom",
"rejectedValue": 1196586930,
"bindingFailure": false,
"code": "JSValidated"
},
{
"codes": [
"NotEmpty.order.id",
"NotEmpty.id",
"NotEmpty.java.lang.String",
"NotEmpty"
],
"arguments": [
{
"codes": [
"order.id",
"id"
],
"arguments": null,
"defaultMessage": "id",
"code": "id"
}
],
"defaultMessage": "may not be empty",
"objectName": "order",
"field": "id",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotEmpty"
}
],
"message": "Validation failed for object='order'. Error count: 2",
"path": "/order"
}
How can I add or change the codes? How can I add or change the list of arguments? Where is all the stuff documented?
You can use a global exception handler using #ExceptionHandler
You can define which exceptions should be handled. You have access to the thrown exception, which contains also the validation errors.
Create your own error class, that contains the properties you want to return.
Map the validation error into your error object and return it along with the HTTP status of your choice.
BindingException is one Exception I got from validation and the handler looks like this :
#ExceptionHandler(BindException.class)
#ResponseBody
public ResponseEntity<Object> handle(HttpServletRequest req, BindException ex) {
ExceptionResponse response = new ExceptionResponse(ex);
return new ResponseEntity<>(response, HttpStatus.EXPECTATION_FAILED);
}
And the error class ExceptionResponse :
#JsonInclude(Include.NON_NULL)
#JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public static class ExceptionResponse{
String exception;
String message;
String trace;
public ExceptionResponse(Exception exception) {
super();
this.exception = exception.getClass().getName();
this.message = exception.getMessage();
this.trace = Arrays.toString(exception.getStackTrace());
}
This is a json serialization of the result of method getErrorAttributes(RequestAttributes requestAttributes,
boolean includeStackTrace) of class
org.springframework.boot.autoconfigure.web.DefaultErrorAttributes.
This class can be extended to add additional properties.
Codes and messages are added by validators, do if you'd like to change them you need to customize validators used.

Categories

Resources