Throw custom Exception with HTTP status code for Jackson Custom deserializer - java

I have this InstantDesrializer
#Slf4j
public class InstantDeserializer extends StdDeserializer<Instant> {
public InstantDeserializer() {
this(null);
}
public InstantDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Instant deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
log.info(node.asText());
TemporalAccessor parse = null;
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Constants.DATE_TIME_FORMAT).withZone(ZoneOffset.UTC);
try {
parse = dateTimeFormatter.parse(node.asText());
} catch (Exception e) {
e.printStackTrace();
throw new IOException();
}
log.info(Instant.from(parse).toString());
return Instant.from(parse);
}
}
And then corresponding IOException in #ControllerAdvice
#ExceptionHandler(IOException.class)
public ResponseEntity<String> handleIOException(IOException e) {
return ResponseEntity.status(422).build();
}
And this in my DTO:
#NotNull
#JsonDeserialize(using = InstantDeserializer.class)
// #DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
private Instant timestamp;
Even when uncommented #DateTimeFormat, it's not working
Ideally, it should return 422 status. But, it returns 400.
Maybe I'm just missing something so small, which I'm not able to figure out.
This approach was suggested here:
Throw custom exception while deserializing the Date field using jackson in java

Your Controller method is never called because JSON body parsing threw an exception.
Your #ContollerAdvice is not applied because the Controller method is not called.
Your handleIOException method is not called, and your 422 status is not applied.
I suspect this is the situation in more detail...
An HTTP request includes a json body.
The controller method matching the #RequestMapping and other annotations of the request takes an instance of your DTO Class as a parameter.
Spring attempts to deserialize the incoming json body before calling your controlling method. It must do this in order to pass the DTO object.
Deserialization uses your custom deserializer which throws an IOException.
This IOException occurs before your controller method is called. In fact, your controller method is never called for this request.
Spring handles the exception using its default behavior, returning an HTTP 400. Spring has a very broad RFC 7231 conception of HTTP 400.
Since your controller method is never called, the #ControllerAdvice is never applied and your #ExceptionHandler does not see the exception. Status is not set to 422.
Why do I believe this?
I frequently see this kind of behavior from Spring, and I think it is the expected behavior. But I have not tracked down the documentation or read the source to be sure.
What can you do about it?
A simple approach which you may not like is to declare your controller method to take inputs that almost never fail, such as String.
You take over the responsibility for validating and deserializing inputs, and you decide what status and messages to return.
You call Jackson to deserialize. Your #ExceptionHandler methods are used.
Bonus: You can return the text of Jackson's often useful parse error messages. Those can help clients figure out why their json is rejected.
I would not be surprised if Spring offers a more stylish approach, a class to be subclassed, a special annotation. I have not pursued that.
What should you do about it?
400 vs. 422 is a case I prefer not to litigate. Depending on your priorities it might be best to accept Spring's convention.
RFC 7231 On Status 400
The 400 (Bad Request) status code indicates that the server cannot or
will not process the request due to something that is perceived to be
a client error (e.g., malformed request syntax, invalid request
message framing, or deceptive request routing).
If the HTTP status code police pick on you, you could point to this and say "I perceive this input to be a client error." Then argue that 422 is inappropriate unless you are serving WebDAV just to keep them off balance.

You do not need an handleIOException method, just add #ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
to your CustomException.
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
#ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public class MyException extends JsonProcessingException {
public MyException(String message) {
super(message);
}
}
So when you make invalid request
with body
{"timestamp":"2018-04-2311:32:22","id":"132"}
Response will be:
{
"timestamp": 1552990867074,
"status": 422,
"error": "Unprocessable Entity",
"message": "JSON parse error: Instant field deserialization failed; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Instant field deserialization failed (through reference chain: TestDto[\"timestamp\"])"
}
With valid request works fine:
{"timestamp":"2018-04-23T11:32:22.213Z","id":"132"}
Response:
{
"id": "132",
"timestamp": {
"nano": 213000000,
"epochSecond": 1514700142
}
}

The exception thrown by Jackson in case of a deserialization error is HttpMessageNotReadableException.
In your custom deserializer, you can throw your own deserialization exception which extends JsonProcessingException.
In your ControllerAdvice you can handle the HttpMessageNotReadableException and get the cause of this which is your custom exception.
This way, you can throw the http code you want.
#ExceptionHandler({HttpMessageNotReadableException.class})
#ResponseBody
public ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex) {
Throwable cause = ex.getCause();
if (cause.getCause() instanceof YourOwnException) {
//Return your response entity with your custom HTTP code
}
//Default exception handling
}

Related

Customizing / patching 400 response in REST API

I've built a rest api using Spring & Swagger. Persistence is through Hibernate. The rest api exposes the data in a different format then it is in the DB, so I'm using the dto pattern. Customer vs. CustomerDto. All is good. I've masked the CustomerDto in Swagger with #ApiModel(value="Customer"). All that is good too.
Here's the problem. I'm using validation annotations like #Size, #NotNull, etc. So I get the nice 400 response json:
{
"timestamp": "2019-11-15T22:25:37.943+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotNull.customerDto.firstName",
Seems like MethodArgumentNotValidException doesn't know about the #ApiModel annotation, so it reveals the object name as customerDto.
Is there any way I can patch the response or anything like that? I know I can catch the 400 exception and build my own response, but I'd rather not re-invent the entire wheel to reproduce that entire json.
My company works with financial institutions, so we're not supposed to reveal app internals for security purposes.
EDIT: Should make it clear that I don't want to REPLACE the stock json, I just want to change the customerDto to customer. There are elements in the json besides the message like the codes and stuff. Trying to get to those too.
All that error message by default is built in DefaultErrorAttributes , so you can extend it to modify the error attributes map.
Here is an example:
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
#Component
public class ExtendedDefaultErrorAttributes extends DefaultErrorAttributes {
#Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
final Map<String, Object> errorAttributes =
super.getErrorAttributes(webRequest, includeStackTrace);
// MethodArgumentNotValidException includes errors key
Object errors = errorAttributes.get("errors");
// .. now you can do whatever you want here... something like completely remove it.
if (errors != null) {
errorAttributes.remove("errors");
}
// In here for what you are looking for , you should be doing something like //below, just as an example, but do it properly, I just typed it without checking:
List<ObjectError> errors = (List<ObjectError>) errorAttributes.get("errors");
errors.get(0).getCodes()[0].replaceAll("customerDto", "customer");
return errorAttributes;
}
}
Or use #ExceptionHandler in ControllerAdvice or Controller class to modify it per how you want.
Example:
#ExceptionHandler({MethodArgumentNotValidException.class})
public void handleMethodArgumentNotValidException(
MethodArgumentNotValidException manve, HttpServletResponse response) throws IOException {
exceptionLog.error(manve.getMessage());
Map<String, String> errors =
manve
.getBindingResult()
.getFieldErrors()
.stream()
.collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
String errorMessage = "...."
if (!errors.isEmpty()) {
ObjectMapper mapper = new ObjectMapper();
errorMessage = mapper.writeValueAsString(errors);
}
response.sendError(400, errorMessage);
}

Java: Custom Global exception handler always returns 200 status

I created a global exception handler for my program which is working and catches my exceptions but when I try to get the status code from the HttpServletResponse, I always get a 200 status. Is there any way I can get the right status from the exception class? Or if you have another work around I'd appreciate it. Can't find anything online and I don't want to hardcode an httpStatus for every exception that goes through my handler. I want to catch all exceptions with this one method. This is the method I created in a my GlobalExceptionHandler class (also fyi I used the #ControllerAdvice annotation for the class):
#ExceptionHandler(value = Exception.class)
public #ResponseBody
ResponseEntity<Object> error(HttpServletRequest request, HttpServletResponse response, Exception e) throws Exception {
log.error("Error in call to " + request.getRequestURI() + " Response: " + response.toString(), e);
ExceptionResource exception = new ExceptionResource();
exception.setLocation(request.getRequestURI());
exception.setStatus(response.getStatus());
exception.setStackTrace(e.getStackTrace());
return new ResponseEntity<>(exception, HttpStatus.valueOf(response.getStatus()));
}
If you are using Spring MVC, your exception needs to have something like this :
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order")
public class OrderNotFoundException extends RuntimeException {
// ...
}
source
ANSWER: I had to think of another implementation but I did find a way to get the status of custom exceptions created using #ResponseStatus annotations. You will want to use this to get the status:
HttpStatus status = AnnotationUtils.findAnnotation(exception.getClass(), ResponseStatus.class).value();
where exception is the exception being passed to the handler.

Http Error Messages Not Propagating from Microservice to Webapp

I'm building a spring boot microservice-based application and I'm having trouble getting my error messages to propagate from the services containing all my business logic back into the webapp. In my services I'm throwing exceptions that look like this:
#ResponseStatus(HttpStatus.BAD_REQUEST)
public class Http400ServiceException extends Exception {
public Http400ServiceException() {
super("Some error message");
}
}
Everything behaves as expected, sending the response code as expected. So if my service sent a 403 exception, I'd get a 403 in the webapp. What I'm trying to do now is to get the error messages from the exceptions in my service.
The Problem
When I poke my services from a rest client in such a way as to produce a 403, the response (in JSON) looks like this:
{
"timestamp": 1459453512220
"status": 403
"error": "Forbidden"
"exception": "com.mysite.mypackage.exceptions.Http403ServiceException"
"message": "Username Rob must be between 5 and 16 characters long"
"path": "/createLogin"
}
However, for some reason I can't access the 'message' field from my webapp. I have some generic error handling code in the webapp that looks like this:
#Override
public RuntimeException handleException(Exception e) {
...
if (e instanceof HttpClientErrorException) {
HttpClientErrorException httpError = (HttpClientErrorException) e;
LOGGER.info(httpError.getResponseBodyAsString());
}
...
}
But when I look in my logs/run my app in debug, httpError.getResponseBodyAsString() is returning null. It's got the right response code, just no response body.
If anyone has any insights into what's going wrong, I'd really appreciate it.
So we parked this issue for a few months while we were working on other areas of the app. But just in case someone else sees this while trying to solve a similar problem, the approach I ended up taking was the following
Create an interface for all responses from all services to implement, and an exception type to indicate that the exception is because of user error:
public interface IModel {
boolean isError();
UserException getError();
}
In the controllers for each service, catch any exceptions and create some sort of IModel out of them:
#ResponseStatus(HttpStatus.OK)
#ExceptionHandler(UserException.class)
public IModel handleException(UserException exception) {
return exception.toModel();
}
In the component used to call the services, if the response has a UserException on it, throw it, otherwise return the response body:
public <T extends IModel> T makeCall(Object payload, Endpoint<T> endpoint) throws UserException {
...
ResponseEntity<T> response = restTemplate.postForEntity(endpoint.getEndpointUrl(), payload, endpoint.getReturnType());
if (response.getBody().isError()) {
throw response.getBody().getError();
}
...
return response.getBody();
}
In the webapp, handle the exceptions with the appropriate #ExceptionHandler e.g.
#ExceptionHandler(UserException.class)
public ModelAndView handleUserException(UserException e) {
ModelAndView modelAndView = new ModelAndView("viewName");
modelAndView.addObject("errorMessage", e.getMessage());
return modelAndView;
}
I kinda feel like there's a cleaner approach but this is the best I've been able to come up with so far. I'll update this if I find a better way of doing it though.

Custom exception handle with spring boot

Here,my requirement is that i want separate code in my application for exception handling,i saw a nice option of spring there using #controller advice to handle exceptions globally.
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
But there i want to cutomization there,like proper dynamic messages,own error code. so how can i do this,i am new to spring boot and even i don't have knowledge of spring.Need basic example.
You can come up with a class like this to capture information to be sent in response in case of exception:-
public class APIResponse {
int errorCode;
String description;
String someInformation;
// any other information that you want to send back in case of exception.
}
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ResponseBody
#ExceptionHandler(DataIntegrityViolationException.class)
public APIResponse handleConflict(DataIntegrityViolationException exception) {
APIResponse response = createResponseFromException(exception);
return response;
}
}
In your controller advice class:-
Have the return type APIResponse instead of void.
The handler method can have the exception raised as the argument.
Using the exception object to create the APIResponse object.
Put #ResponseBody on the handler method.

Is it good practice to throw exception from DAO layer to controller?

I am writing one REST api. There might be two exceptions in my DAO layer namely Exception_X and Exception_Y. If I encountered a exception Exception_X in DAO layer, my controller should return status code 200, if Exception_Y then 401 and if all goes well controller should return 201.
Now what was I thinking that I will throw encountered exception as it is from DAO layer to controller via service layer and in catch block of controller I will return response.
Is it acceptable or there is some other standard way?
Yes that is quite an acceptable way. However, rather than using try-catch, I would suggest to implement Exception Handlers for your REST Controllers. That way, you won't have to clutter your REST methods.
Also, it would be better to create a model object in REST layer for Error messages - ErrorResponse, with appropriate information:
class ErrorResponse {
int statusCode;
String errorMessage;
}
And return it's object from the exception handlers. BTW, you can also map your exception class directly to a response using #ResponseStatus annotation:
#ResponseStatus(value=401, reason="message")
class Exception_Y extends RuntimeException {
}
Then you don't have to write exception handler for that exception.
My suggestion would be wrap any unchecked exceptions with a service layer for loose coupling, and clean abstraction. Keep your controller free from conditions and let Service layer take care of this pain.
Keeping security concern in mind if you exposing it externally wrap your exception with service oriented exception it also helps to achieve generic layer specific exceptions say PersistentException, ServiceException etc. keeping good degree of decoupling in mind.
For handling exception globally you can use spring inbuild ControllerAdvice annotation with JsonExceptionModel for formatted error response.
#ControllerAdvice
public class GlobalExceptionHandler {
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(SQLException.class)
public Map<String, Object> handleSQLException(HttpServletRequest request, Exception ex) {
//json response here
}
}
public class JsonExceptionModel {
private int code;
private String type;
private String url;
private String message;
private String moreInfo;
// getters/setters here
}
I suggest you to go with Exception Resolver which is providing by spring.
Spring Framework provides HandlerExceptionResolver interface that we can implement to create global exception handler. We can also override it to create our own global handler with our application specific changes, such as logging of exception messages.
Here is the sample implementation of HandlerExceptionResolver,which will fullfill your need
public class RestResponseStatusExceptionResolver extends HandlerExceptionResolver {
#Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
if (ex instanceof InvalidInputException) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return handleException(ex);
} else if (ex instanceof ResourceNotFoundException) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return handleException(ex);
}
//Adding error details to modelView object
modelAndView.addObject("errors", ErrorDetails);
// Custom error message details
public class ErrorDetails {
private String code;
private List<String> data;
}

Categories