-1

I've defined a custom JsonSerializer in my spring web application (NOT Spring Boot).

public class CalendarSerializer extends StdSerializer<Calendar>
{
    public CalendarSerializer()
    {
        super(Calendar.class);
    }

    /** {@inheritDoc} */
    @Override
    public void serialize(Calendar value, JsonGenerator gen, SerializerProvider serializers) throws IOException
    {
        ZoneId zoneId = TimeZone.getDefault().toZoneId();
        return value.toInstant().atZone(zoneId);
    }
}

It has been included in the json mapper definition and if no exception is thrown in the serialize() method, all works good.

To handle errors, I've created a ResponseEntityExceptionHandler

@ControllerAdvice
public class RestApiExceptionHandler extends ResponseEntityExceptionHandler
{
    private static final Logger log = LoggerFactory.getLogger(RestApiExceptionHandler.class);

    @ExceptionHandler(RestApiException.class)
    protected ResponseEntity<RestApiErrorResponse> handleRestApiException(RestApiException ex)
    {
        RestApiErrorResponse apiErrorResponse = ex.getRestApiErrorResponse();
        log.error("RestApiException occurs.", ex);
        return new ResponseEntity<>(apiErrorResponse, apiErrorResponse.getHttpStatus());
    }

    [...]

    @ExceptionHandler(Exception.class)
    protected ResponseEntity<RestApiErrorResponse> handleUnexpectedError(Exception ex)
    {
        RestApiErrorResponse unexpectedError = RestApiErrorResponse.getUnexpectedError();
        log.error("Unexpected exception. {}", unexpectedError, ex);
        return new ResponseEntity<>(unexpectedError, unexpectedError.getHttpStatus());
    }
}

It handles some custom exceptions, but anyway any kind of exception is managed by handleUnexpectedError and the caller correctly receives the serialization of RestApiErrorResponse.

However, if an exception occurs in the CalendarSerializer, the ResponseEntityExceptionHandler is bypassed and the caller receives the default tomcat error page with the stacktrace

Is there a way to manage the exception thrown by the JsonSerializers so that the caller receives a custom json with the error details?

Here's the stacktrace

org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: (was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.foo.dataobjects.Holiday["startDate"])
    org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:296)
    org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:103)
    org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:290)
    org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:180)
    org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:82)
    org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:119)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
    org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    com.foo.rest.web.RestApiAuthenticationFilter.doFilter(RestApiAuthenticationFilter.java:122)
Root Cause

com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.foo.dataobjects.Holiday["startDate"])
    com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:394)
    com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:353)
    com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:316)
    com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:727)
    com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
    com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:145)
    com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:107)
    com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:25)
    com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
    com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:400)
    com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1392)
    com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:913)
    org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:287)
    org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:103)
    org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:290)
    org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:180)
    org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:82)
    org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:119)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
    org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    com.foo.rest.web.RestApiAuthenticationFilter.doFilter(RestApiAuthenticationFilter.java:122)
Root Cause

java.lang.NullPointerException
    com.foo.rest.serializers.CalendarSerializer.serialize(CalendarSerializer.java:44)
    com.foo.rest.serializers.CalendarSerializer.serialize(CalendarSerializer.java:1)
    com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer.serialize(StdDelegatingSerializer.java:168)
    com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
    com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
    com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
    com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:145)
    com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:107)
    com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:25)
    com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
    com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:400)
    com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1392)
    com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:913)
    org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:287)
    org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:103)
    org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:290)
    org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:180)
    org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:82)
    org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:119)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
    org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    com.foo.rest.web.RestApiAuthenticationFilter.doFilter(RestApiAuthenticationFilter.java:122)

UPDATE: Resolved adding to the RestApiExceptionHandler the method

@Override
protected ResponseEntity<Object> handleHttpMessageNotWritable(
    HttpMessageNotWritableException ex,
    HttpHeaders headers,
    HttpStatus status,
    WebRequest request)
{
    String details = "Error exporting the results of the incoming request.";
    RestApiErrorResponse error = new RestApiErrorResponse(details, RestApiErrorCode.SERIALIZATION_ERROR);

    String uri = getUri(request);
    log.error("Error serializing the results for request {}. {}", uri, error, ex);
    return new ResponseEntity<>(error, error.getHttpStatus());
}
Syscall
  • 19,327
  • 10
  • 37
  • 52
Maura
  • 15
  • 4
  • The `java.util` date-time API is outdated and error-prone. It is recommended to stop using it completely and switch to the [modern date-time API](https://www.oracle.com/technical-resources/articles/java/jf14-date-time.html). – Arvind Kumar Avinash Mar 27 '21 at 09:39
  • Hi @ArvindKumarAvinash , I wasn't searching the way to fix the NPE, neither a way to handle with Calendars. The NPE was artificial, just to have an exception on the JsonSerializer The Calendars are unfortunately mandatory for legacy problem. The solution that suited for me is the one I've mentioned in the UPDATE section. – Maura Mar 29 '21 at 14:37
  • Maura - Please post your update as an answer. After 48 hours, you can even accept your own answer. This will be helpful to future visitors of this page. – Arvind Kumar Avinash Mar 29 '21 at 15:02
  • 1
    @ArvindKumarAvinash done, thank you – Maura Mar 31 '21 at 12:56

1 Answers1

0

Resolved by myself adding to the RestApiExceptionHandler the method

@Override
protected ResponseEntity<Object> handleHttpMessageNotWritable(
    HttpMessageNotWritableException ex,
    HttpHeaders headers,
    HttpStatus status,
    WebRequest request)
{
    String details = "Error exporting the results of the incoming request.";
    RestApiErrorResponse error = new RestApiErrorResponse(details, RestApiErrorCode.SERIALIZATION_ERROR);

    String uri = getUri(request);
    log.error("Error serializing the results for request {}. {}", uri, error, ex);
    return new ResponseEntity<>(error, error.getHttpStatus());
}
Maura
  • 15
  • 4