how to catch exception in spring boot rest api - json

i have a restcontroller with following Code
#RequestMapping(method = RequestMethod.POST, value = "/student")
public void addTopic(#RequestBody Student student) {
student.setPassword(bCryptPasswordEncoder.encode(student.getPassword()));
studentService.addStudent(student);
}
but if the json data doesn't match the Student object, or is wrong formatted an com.fasterxml.jackson.core.JsonParseException: Unexpected character ('"' (code 34)) ist thrown.
what is the best practice to prevent that

I've found that I need to catch JsonProcessingException (which JsonParseException extends from) in the #ExceptionHandler rather than JsonParseException
#ControllerAdvice
public class FeatureToggleControllerAdvice {
#ExceptionHandler(JsonProcessingException.class)
public ResponseEntity<JSONAPIDocument> handleJsonParseException(JsonProcessingException ex) {
final Error error = new Error();
error.setId(UUID.randomUUID().toString());
error.setStatus(HttpStatus.BAD_REQUEST.toString());
error.setTitle(ex.getMessage());
return new ResponseEntity<>(JSONAPIDocument
.createErrorDocument(Collections.singleton(error)), HttpStatus.NOT_FOUND);
}
}
Using JsonParseException in the above sample and nothing is caught, but using JsonProcessingException works as expected.

Use Spring ExceptionHandler to do that

You could specify an ExceptionHandler based on Exception types and also apply the error codes you want to use.
#ExceptionHandler(JsonParseException.class)
public JacksonExceptionHandler {
public ResponseEntity<String> handleError(final Exception exception) {
HttpStatus status = HttpStatus.BAD_REQUEST;
if (exception != null) {
LOGGER.warn("Responding with status code {} and exception message {}", status, exception.getMessage());
return new ResponseEntity<>(exception.getMessage(), status);
}
}
Furthermore you could make use of javax.validation to validate the entity you receive and then Spring Boot will do all the validation automagically. Just add #Valid to the body.

Related

ResponseExceptionMapper in cxf using Client (javax.ws.rs.client.Client) API

This post does not resolve the issue: ResponseExceptionMapper in cxf client . You will notice that I did in fact register and annotate my Provider, and I tried with WebApplicationException as suggested instead of Exception/CustomException.
Problem Statement: Unable to implement custom client side exception handler using Client (javax.ws.rs.client.Client) API, and #Provider class implementing the ResponseExceptionMapper interface.
Questions:
Does Client API not support custom client side providers for exception handling?
Any literature I looked up for this problem statement uses JAXRSClientFactory implementation; I'm yet to find any using Client API for this scenario. Would I have to switch my implementation?
What is the difference between Client API and JAXRSClientFactory implementations?
I am working on a cxf Client API implementation in Java, and noticed that for http status codes above 300 cxf wraps the Response in either a WebApplicationException or ProcessingException (depending upon the response status code). The server in my case has a customized response body indicating the actual reason for an http status code !200, like below (for response code = 412):
{
"requestError": {
"serviceException": {
"messageId": "SVC4120",
"text": "Invalid Request: Invalid Coupon Code."
}
}
}
Unfortunately the WebApplicationException itself does not render this. Instead the only message captured in the exception directly is a generic "412 Precondition Failed". I can do something similar to below exception block from code snippet (includes Client API code snippet):
protected RESPOBJ invoke(String endPointUrl) throws CustomException {
Object reqPOJO = prepareRequest();
try {
if(client == null) {
ClientBuilder builder = ClientBuilder.newBuilder();
//register custom JAX-RS components
builder.register(new CustomMapper());
}
WebTarget target = client.target(endPointUrl);
//do this when queryParams exist
if(!getUriParams().isEmpty()) {
for(Map.Entry<String, String> queryParams : getUriParams().entrySet()) {
target = target.queryParam(queryParams.getKey(), queryParams.getValue());
}
}
Invocation.Builder builder = target.request();
//create headers here
MultivaluedMap<String, Object> headers = new MultivaluedHashMap<>();
if(isBasicAuthRequired()) {
headers.add(AUTH_HEADER_PARAM, getBasicAuthentication());
}
headers.add(CONTENT_TYPE, getMediaType().toString());
builder.headers(headers);
builder.accept(getMediaType().toString());
//GET or POST
if(HttpMethodType.GET.equals(getHttpMethod())) {
return builder.get(RESPOBJ.class);
}
return builder.post(Entity.entity(reqPOJO, getMediaType()), RESPOBJ.class);
}
catch (Exception ex) {
if(ex instanceof ResponseProcessingException) {
ResponseProcessingException e = (ResponseProcessingException) ex;
logger.error("Unmarshalling failed: [" + e.getResponse().readEntity(String.class) + "]");
}
else if(ex instanceof WebApplicationException) {
WebApplicationException e = (WebApplicationException) ex;
logger.error("Error Response: ["+e.getResponse().readEntity(String.class) + "]");
}
throw new CustomException(ex);
}
}
However, I am looking to implement something cleaner, preferably using a custom Exception handler that implements ResponseExceptionMapper<> interface. From literature I noticed the only implementations of ResponseExceptionMapper for custom client side exception handling are using JAXRSClientFactory. My current implementation however uses the Client API (code snippet below). From a design aspect I will modify this to have a separate CustomExceptionMapper class that would be the Provider only for Exception cases, but I do not see why this Custom class is registered as a Provider (works for 200 status codes as MBR, and the MBW works always) but does not work for exception cases.
Update: While debugging and observing changes between a 200 vs >300 status code (412 in my case), I noticed that for 200 case JAXRSUtils.readFromMessageBodyReader() method gets invoked, which for the 1st time retrieves the Custom Provider. The code never gets here for status codes shown below in code snippet which should be the reason for not finding the CustomMapper. Is there any difference in how I must register my CustomExceptionMapper? Or does the Client API simply not support this functionality?
// for failure case the method above returns null (status > 300), whereas for success 200 case it executes method in last line and gets the provider.
// AbstractClient class that invokes the doReadEntity() method which in turn invokes and finds the Provider in JAXRSUtils.readFromMessageBodyReader() method code
protected <T> T readBody(Response r, Message outMessage, Class<T> cls,
Type type, Annotation[] anns) {
if (cls == Response.class) {
return cls.cast(r);
}
int status = r.getStatus();
//this is invoked for failure case
if ((status < 200 || status == 204) && r.getLength() <= 0 || status >= 300) {
return null;
}
//this for 200 status code
return ((ResponseImpl)r).doReadEntity(cls, type, anns);
}
//My custom provider code
#Provider
#Consumes
#Produces(MediaType.APPLICATION_JSON)
public class CustomMapper implements MessageBodyReader<CustomResponse>, MessageBodyWriter<CustomRequest>, ResponseExceptionMapper<CustomException> {
private Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
#Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type.isAssignableFrom(CustomResponse.class);
}
#Override
public CustomResponse readFrom(Class<CustomResponse> type, Type genericType, Annotation[] annotations,
MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
CustomResponse respObj = new CustomResponse();
//json to pojo code
return respObj;
}
#Override
public long getSize(CustomRequest reqObj, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return -1;
}
#Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type.isAssignableFrom(CustomRequest.class);
}
#Override
public void writeTo(CustomRequest reqObj, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
entityStream.write(gson.toJson(reqObj).getBytes());
}
#Override
public CustomException fromResponse(Response exceptionResponse) {
//Response obj to my CustomException code
return (CustomException);
}
}
Questions:
I'm trying to figure out what is done wrong here, and if Client API does not support custom client side exception handling for any reason?
What is the difference between Client API and JAXRSClientFactory implementations?
I also am looking into possibly using ClientResponseFilter (haven't tried this yet).
Any help appreciated. Thanks.

How to handle all not handled exceptions in #ControllerAdvice?

I use this exception handler to handle some specific exceptions in my Spring boot application (REST API):
#ControllerAdvice
class GlobalExceptionHandler {
#ExceptionHandler(NotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public
#ResponseBody
ResponseMessage notFound(NotFoundException ex) {
return new NotFoundResponseMessage(ex.getMessage());
}
#ResponseStatus(value = HttpStatus.UNSUPPORTED_MEDIA_TYPE)
#ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public
#ResponseBody
ResponseMessage unsupportedMediaType(HttpMediaTypeNotSupportedException ex) {
return new UnsupportedMediaTypeResponseMessage(ex.getMessage());
}
#ExceptionHandler(UnauthorizedException.class)
#ResponseStatus(HttpStatus.UNAUTHORIZED)
public
#ResponseBody
ResponseMessage unauthorized(UnauthorizedException ex) {
return new UnauthorizedResponseMessage(ex.getMessage());
}
#ExceptionHandler(HttpRequestMethodNotSupportedException.class)
#ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
public
#ResponseBody
ResponseMessage methodNotAllowed(HttpRequestMethodNotSupportedException ex) {
return new MethodNotAllowedResponseMessage(ex.getMessage());
}
#ExceptionHandler(ForbiddenException.class)
#ResponseStatus(HttpStatus.FORBIDDEN)
public
#ResponseBody
ResponseMessage forbidden(ForbiddenException ex) {
return new ForbiddenResponseMessage(ex.getMessage());
}
}
and I would like to handle all the others exceptions with one "global" handling method. But I need to get HTTP status code in this method to process error message etc.
Question
Is there some way how to redirect all non-handled exceptions into one particular method? How can I do it?
See the docs:
Any Spring bean declared in the DispatcherServlet’s application
context that implements HandlerExceptionResolver will be used to
intercept and process any exception raised in the MVC system and not
handled by a Controller.
public interface HandlerExceptionResolver {
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
}

Wildfly: ExceptionMapper not triggered with RestEasy JSR-303 Bean Validation

I'm using Bean Validation with RestEasy in Wildfly 8.2.0.Final:
#Path("/user")
#Produces(MediaType.APPLICATION_JSON)
public class UserEndpoint
{
//more code
#GET
#Path("/encrypt/{email}")
public Response fetchEncryptedId(#PathParam("email") #NotNull String email)
{
String encryptedUserId = userService.getEncryptedUserId(email);
return Response.ok().entity(new UserBo(encryptedUserId)).build();
}
}
This basically works. Now I'd like to get the response as JSON object but I can't get it working. All my "application" exceptions are handled by my Exception Mapper, this works:
#Provider
public class DefaultExceptionMapper implements ExceptionMapper<Exception>
{
private static final String MEDIA_TYPE = "application/json";
private LoggingService loggingService;
#EJB
public void setLoggingService(LoggingService loggingService)
{
this.loggingService = loggingService;
}
#Override
public Response toResponse(Exception exception)
{
ResponseObject responseObject = new ResponseObject();
responseObject.registerExceptionMessage(exception.getMessage());
if (exception instanceof ForbiddenException)
{
loggingService.log(LogLevel.ERROR, ((ForbiddenException)exception).getUserId(), ExceptionToStringMapper.map(exception));
return Response.status(Status.FORBIDDEN).type(MEDIA_TYPE).entity(responseObject).build();
}
//more handling
loggingService.log(LogLevel.ERROR, "", ExceptionToStringMapper.map(exception));
return Response.status(Status.INTERNAL_SERVER_ERROR).type(MEDIA_TYPE).entity(responseObject).build();
}
}
But bean validation somehow bypasses it. Then I thought about using Throwable instead of Exception but it didn't help either. I guess the ExceptionMapper is not triggered because there is some life cycle problem with JAX-RS and JSR303. But how can I syncronize them to handle bean validation exceptions?
Additional information: The exception passes the javax.ws.rs.container.ContainerResponseFilter so I could write some workaround by implementing the filter method in a subclass, but this is not clean solution. The target is to handle the exceptions in the Exception mapper.
It's not always the case that your ExceptionMapper<Exception> will catch all exception under the Exception hierarchy. If there is another more specific mapper, say one for RuntimeException, that mapper will be used for all exception of RuntimeException and its subtypes.
That being said (assuming you're using resteasy-validation-provider-11), there is already a ResteasyViolationExceptionMapper that handles ValidationException.
#Provider
public class ResteasyViolationExceptionMapper
implements ExceptionMapper<ValidationException>
This mapper is automatically registered. It returns results in the form of a ViolationReport. The client needs to set the Accept header to application/json in order to see a response similar to
{
"exception":null,
"fieldViolations":[],
"propertyViolations":[],
"classViolations":[],
"parameterViolations":[
{
"constraintType":"PARAMETER",
"path":"get.arg0",
"message":"size must be between 2 and 2147483647",
"value":"1"}
],
"returnValueViolations":[]
}
You can see more at Violation reporting.
If you want to completely override this behavior, you can create a more specific mapper for ResteasyViolationException, which is the exception thrown by the RESTeasy validator
#Provider
public class MyValidationMapper
implements ExceptionMapper<ResteasyViolationException> {
#Override
public Response toResponse(ResteasyViolationException e) {
}
}

why Spring throws HttpMessageNotReadableException with different CAUSES for JSON

I have a exception handler controller where I am catching HttpMessageNotReadableException as below:
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ResponseBody
protected ErrorMessage handleJsonException(final HttpMessageNotReadableException ex, final HttpServletRequest request)
{
if (ex.getCause() instanceof JsonParseException)
{
// some code
}
if (ex .getCause() instanceof JsonMappingException)
{
// some code
}
}
i got different causes for POST and PUT with malformed json(the first double quotes is missing in JSON text)
{firstName":"abc","lastName":"xyz"}
POST - JsonParseException
PUT - JsonMappingException
I believe both should have the same cause "JsonParseException" as the syntax is wrong.
Can anyone suggest why spring gives different for PUT as "JsonMappingException".
Looking for something to resolve this kind a problem, I found this article -> http://www.jayway.com/2013/02/03/improve-your-spring-rest-api-part-iii/
It has a few ideas to workaround, like using "getMostSpecificCause". I'm reading it to solve my problem.
Try this
//ex -> HttpMessageNotReadableException
Throwable throwable = ex.getCause();
JsonMappingException jsonMappingException = ((JsonMappingException) throwable);
// import 'InvalidFormatException' from com.fasterxml.jackson.databind.exc package
List<JsonMappingException.Reference> references = ((InvalidFormatException)throwable).getPath();
for (JsonMappingException.Reference reference : references) {
if (reference.getFieldName() != null) {
field += reference.getFieldName() + ".";
}
}
String message = jsonMappingException.getOriginalMessage();

How do I add errors to my Spring MVC REST Service?

How can I change/update the following REST call from Spring MVC to return a error if the user did not enter of the the two names I was coding for.. something like a not found?
#RequestMapping(value = "/{name}", method = RequestMethod.GET)
#ResponseBody
public User getName(#PathVariable String name, ModelMap model)
{
logger.debug("I am in the controller and got user name: " + name);
/*
Simulate a successful lookup for two users. This
is where your real lookup code would go.
*/
if ("name2".equals(name))
{
return new User("real name 2", name);
}
if ("name1".equals(name))
{
return new User("real name 1", name);
}
return null;
}
Define a new exception class, e.g. ResourceNotFoundException and throw an instance of this from your annotated controller method getName.
Then also define an annotated exception handler method in your Controller class to handle that exception, and return a 404 Not Found status code, potentially logging it.
#ExceptionHandler(ResourceNotFoundException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public void handleResourceNotFoundException(ResourceNotFoundException ex)
{
LOG.warn("user requested a resource which didn't exist", ex);
}
Or even returning some error message, using #ResponseBody annotation:
#ExceptionHandler(ResourceNotFoundException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND)
#ResponseBody
public String handleResourceNotFoundException(ResourceNotFoundException ex)
{
return ex.getMessage();
}
You could create an object specifically for returning error responses. That way, you can say whatever you want. For example:
#ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ResponseStatus> handleHttpMessageNotReadableException(HttpMessageNotReadableException ex){
ResponseStatus responseStatus = new ResponseStatus("400", "Bad Request. " + ex);
responseStatus.setResponseStatusTime(timestampService.createTimestamp());
HttpStatus status = HttpStatus.BAD_REQUEST;
ResponseEntity<ResponseStatus> response = new ResponseEntity<ResponseStatus>(responseStatus, status);
return response;
}
In this example you can see that there is a ResponseStatus object. This object contains a field for a status code, status message, date and time. You may not need date and time but I find it useful for when someone sends me an error they have seen, because then it is easy to track down exactly where it happened in our server logs.