Catching any exception Jackson throws with a single ExceptionMapper - java

I have a JAX-RS project that uses Jackson to handle JSON conversions.
When Jackson throws an exception, it automatically returns a string with the error description.
Since I want to return a custom JSON object, I created an ExceptionMapper.
The problem is, it only catches the exception when I specify exactly the type of the Exception being thrown.
For example, when the JSON sent to a method contains an unknown property, this works:
public class MyExceptionMapper implements ExceptionMapper<UnrecognizedPropertyException>
But if I change UnrecognizedPropertyException to PropertyBindingException (which the first extends), it won't work.
In short:
How can I create a generic exception mapper that catches any exception thrown by Jackson (or any other component of my app, for that matter)?

try with
public class MyExceptionMapper implements ExceptionMapper<Exception>
This should be the fallback for all Exceptions.
Jackson is looking for the the hierarchie from the exception up if it finds a suitable ExceptionMapper.
It looks as long as there is something in the type hierarchy.
So UnrecognizedPropertyException will be handled by PropertyBinding-Exception mapper but not the other way round because UnrecognizedPropertyException Mapper is more specific there could be many subclasses and then there cannot be determined which Mapper to take. So it only works upwards.
An because Exception is the base Exception everything ends up there.

Related

Spring Boot: Custom exception or Validation for file uploading?

For my file (csv) upload endpoint, I check the file type using a method in my CsvHelper class:
private static String[] TYPES = {"text/csv", "application/vnd.ms-excel"};
public static boolean hasCsvFormat(MultipartFile file) {
return Arrays.stream(TYPES).anyMatch(file.getContentType()::equals);
}
And call it from service as shown below:
public void create(MultipartFile file) throws Exception {
if (!CsvHelper.hasCsvFormat(file)) {
throw new NotValidFormatException(...);
}
// ...
}
I created a custom exception for this called NotValidFormatException using #ControllerAdvice, but I am not sure if it is the most proper way for this.
My questions:
1. Should I create custom exception or custom validator as mentioned on this? But as I have not a model field, I need to use #Valid on the request and not sure if I can use that approach to verify file type (by calling my hasCsvFormat() method.
2. I used this approach for creating custom exception handling. If I wanted to use that approach for this scenario, should I create a separate class (e.g. NotValidFormatException) like NoSuchElementFoundException on that example? Or should I include a new exception method to the GlobalExceptionHandler class as a common exception type?
Based on your requirement, option number 2 is more suitable from my perspective.
You can create separate class for NotValidFormatException , annotate with ResponseStatus of your need & extend it with RuntimeException.
Now, your GlobalExceptionHandler is general purpose handler which perform actions once specific exception is thrown.
So you have to create method which should annotate with #ExceptionHandler(NotValidFormatException.class) in GlobalExceptionHandler .
Benefits of having separate class for NotValidFormatException is you can customize error message, can perform more operation within methods of that class.
If your requirement is minimal(like logging & returning response etc.) , then I would suggest to have only single ExceptionHandler method in GlobalExceptionHandler which will handle most of exceptions i.e. logging & returning response.

How to create working custom JSON parse exception mapping?

I am using jersey-media-json-jackson-2.27 which has internal jackson.jaxrs.base where is JsonParseExceptionMapper which is used to send down a "400 Bad Request in the event unparsable JSON is received. But I want my own custom mapper.
I've created my own custom exception mapper.
I've already tried to set annotation #Priority(1), register it in my ResourceConfig like register(ApplicationJsonExceptionMapper.class) and with register(ApplicationJsonExceptionMapper.class, 1). Also I have register(JacksonJaxbJsonProvider.class).
package com.application.provider;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.core.JsonProcessingException;
#Provider
public class ApplicationJsonExceptionMapper implements
ExceptionMapper<JsonProcessingException> {
#Override
public Response toResponse(JsonProcessingException
jsonProcessingException) {
String responseMessage = "Invalid JSON";
return Response.status(Response.Status.BAD_REQUEST).entity(responseMessage).type("text/plain").build();
}
}
Unfortunately when I send invalid JSON it still uses the built-in JsonParseExceptionMapper, do you have any ideas to try?
JsonProcessingException is the superclass of JsonParseException. So it makes sense that the mapper for JsonParseException is called for a JsonParseException being thrown. If you want to override it, then create your own mapper for JsonParseException. You will also need to create one for JsonMappingException as Jackson also has one for this also.
And Just FYI, so you understand how mappers are chosen, here's a quote from the JAX-RS spec
When choosing an exception mapping provider to map an exception, an implementation MUST use the provider whose generic type is the nearest superclass of the exception.

Jackson ignore #Size when serializing

I have a situation in which I want to return a request to a user if it fails validation along with an appropriate error message. The problem that I've run across is that I'm using Jackson to deal with the JSON request, and the failure in validation also causes Jackson to fail to serialize the message.
For instance, if I have this object:
public class SomeRequest
{
#Size(min=1, max=10)
private String someField;
//getter and setter here
}
...when I go to serialize when the field is invalid (let's say it has 11 characters in it)...
ObjectMapper mapper = new ObjectMapper();
output = mapper.writeValueAsString(someRequestInstance);
...I get a JsonProcessingException (Error(s) validating object). I've confirmed that a valid object has no problem with serialization. So my question is this: How do I get Jackson to ignore the javax.validation annotations when serializing?
Thanks in advance!
By default Jackson does not invoke bean validation (javax.validation.constraints) on JSON serialization. Either default behavior is overridden/customized or there's any kind of interceptor/hook which does that.
Research the stack trace in order to find out where exactly exception occurs and dig around that.

JAX-RS : Suppress Error Message

I have a class which takes enum values like Male,Female #POST . when I sent a wrong value like 'male' instead of 'Male' it shows me 400 Bad Request with this message in rest client : Can not construct instance of constants.Constants$GenderEnum from String value 'male': value not one of declared Enum instance names
at [Source: org.apache.catalina.connector.CoyoteInputStream#718a453d; line: 7, column: 23] (through reference chain: valueobjects.ConsumerValueObject["gender"])
My Rest End Point Looks like below :
#Consumes("application/json")
#Produces("application/json")
#POST
public Response addConsumer(ConsumerValueObject consumerVO)
Here ConsumerValueObject holds the enum.
How to suppress that error message in Rest client? I tried with ExceptionMapper but it did not help!I need to suppress the message due to security issues!
This is the Jackson response from either JsonParseExceptionMapper or JsonMappingExceptionMapper. These classes come with the dependency
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>${2.x.version}</version>
</dependency>
Whether you have this explicit dependency or you have the resteasy-jackson2-provider (which uses the above under the hood), most likely the mappers are registered implicitly through classpath scanning. For instance you have an empty Application class.
#ApplicationPath("/")
public class ResteasyApplication extends Application {}
This will cause disovery/registration through classpath scanning. If you don't have either of those dependencies, and if you are in Wildfly, I am not exactly sure how they are registered, but that is what's happening.
You could write/register your own ExceptionMappers for the JsonParseException and JsonMappingException
#Provider
public class JsonMappingExceptionMapper
implements ExceptionMapper<JsonMappingException> {
#Override
public Response toResponse(JsonMappingException e) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
}
but from what I have tested, it's a tossup as to which one will be registered, yours or Jackson's. The mappers are put into a Set (so unordered), then pushed into a Map, so only one get's pushed in. The order in which they are pushed in like I said is a tossup.
I guess this is really only a partial answer, as I have not been able to find a solution that is guaranteed to use your mapper, aside from registering all your classes explicitly (ultimately disabling the classpath scanning), but that is a hassle.
But now the fight has been narrowed down. I will try again some more if I get a chance later
UPDATE
So this is not a solution, just a semi-proof-of-concept to show how we can get it to use our ExceptionMapper.
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.my.pkg.JsonMappingExceptionMapper;
#Path("/init")
public class InitResource {
#GET
public Response init() {
ResteasyProviderFactory factory = ResteasyProviderFactory.getInstance();
factory.getExceptionMappers().put(JsonMappingException.class,
new JsonMappingExceptionMapper());
return Response.ok("Done!").build();
}
}
Once we hit the init endpoint for first time, our JsonMappingExcpetionMapper will register, and override the existing one, whether it is Jackson's or ours.
Of course we would not want to do this for real, it's just showing how to override the mapper. The thing I can't figure out is where to put this code. I've tried a ServletContextListener, in the Application constructor, in a Feature with a low priority. I can't figure it out. None of the above occur before RESTeasy does its final registration.
Do you really want to supress the error message or do you want to fix the actual probelm?
You can actually catch all thrown exception with a custom exception mapper like
#Provider
public class CustomExceptionMapper implements ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable t) {
return Response.ok().build();
}
}
though, this will handle all caught exceptions and return a 200 OK which tricks clients to think that the request actually succeeded - which was not the case! Instead of Throwable you should be able to catch the concrete exception (even if it is a RuntimeException) as well - maybe you have not declared it as provider or did not specify the correct exception class?
Though, as already mentioned returning a different status code for an exception is generally bad practice and should be avoided. Fixing the actual problem is probably more suitable in that case.
JAX-RS provides MessageBodyReader and MessageBodyWriter interfaces which you can declare to un/marshall an inputstream to an object or an object to return to an output-stream. The official documentation on MessageBodyReader has more detailed information on that regard.
One implementation therefore could be the following steps:
Read the input-stream to f.e. string
Replace all "male" or "female" tokens with their upper-case version
Parse the string to a json-representation (using org.json.JSONObject f.e)
Use ObjectMapper to convert the JSON representation to a Java object
return the mapped object
This works if the input failure is just a simple upper/lower case issue. If there are typos or semantically alternative available, which are not yet in your enum, you need to put in a bit more effort.
If you, however, fail to create a proper object representation, you should return a user-failure (something in the 400 range) to the client to inform the client that something went wrong.

What comes first - InjectableProvider(s) or ExceptionMapper(s)?

I haven't found an answer to the question above. How can I throw an exception from InjectableProvider, and get caught in ExceptionMapper, in order to provide a custom response?
Alex, Pavel,
I have tried this myself with:
a Joda DateTime injectable provider: PerRequestTypeInjectableProvider<PathParam, DateTime>
a mapper that converts exceptions into objects serializable in a format accepted by the client (XML, JSON, etc.): ExceptionMapper<WebApplicationException>
Based on my experiment, it seems that any WebApplicationException thrown in the InjectableProvider bypasses the exception mapper, and is therefore not properly formatted.
I have also tried:
adding an ExceptionMapper<ParamException>,
renaming the classes hoping the order in which these are wired follows alphabetical order,
but no luck so far.
#Pavel / anyone from Jersey: any advice would be welcome!
Thanks,
Marc.
UPDATE
If you wrap the logic in your InjectableProvider in a try/catch block, then in the catch block, the trick is to:
create your serializable POJO / your custom response,
wrap it into a javax.ws.rs.core.Response with the appropriate status code and/or media type,
pass it to the WebApplicationException, as the entity parameter.
#Pavel / anyone from Jersey: any alternative which would avoid this additional logic and make things "just work"?

Categories