3

I have a web application where the server returns any results in JSON format. For creating the JSON, I use the codehaus Jackson ObjectWriter, version 1.9.8.

The problem I'm having that sometimes there is an error, in the mapping, and from then on all server calls result in an error. I haven't been able to determine what causes the error, I did discover the origin.

When the exception occurs, the server returns "(was java.lang.ArrayIndexOutOfBoundsException) (through reference chain: com.onior.modm.restlet.helpers.ServiceResult["success"])", which means that the exception thrown in 'toJSON' was catched and mapped to JSON by 'toRepresentation', otherwise it would have returned empty.

I just don't know why it is sometimes failing. I will be able to use the application all morning without problems, and then suddenly I will get this error. It happens on different calls, but these calls will succeed at other times. From my point of view it seems quite random, but maybe someone can help me see the light? :)

The server result that is being mapped:

public class ServiceResult<T> {
    private boolean success;
    private T results = null;
    private ModmServiceStatus message = null;

    public ServiceResult() {
    }

    public ServiceResult(T results) {
        this.success = true;
        this.results = results;
    }

    public ServiceResult(boolean success, ModmServiceStatus message) {
        this.success = success;
        this.message = message;
    }

    public ServiceResult(ServiceError error) {
        this(false, new ModmServiceStatus(error));
    }

    public static ServiceResult<ModmServiceStatus> serviceException(
        ServiceError error) {
        return new ServiceResult<ModmServiceStatus>(false,
            new ModmServiceStatus(error.getCode(), error.getDescription()));
    }

    public static ServiceResult<ModmServiceStatus> dbError() {
        return ServiceResult
            .serviceException(ServiceError.GENERIC_DATABASE_ERROR);
    }

    public static ServiceResult<ModmServiceStatus> invalidJson() {
        return ServiceResult
            .serviceException(ServiceError.GENERIC_INVALID_JSON);
    }

    public static ServiceResult<ModmServiceStatus> missingEntity() {
        return ServiceResult                .serviceException(ServiceError.GENERIC_MISSING_OR_INCOMPLETE_ENTITY);
    }

    public static ServiceResult<ModmServiceStatus> entityNotFound() {
        return ServiceResult
            .serviceException(ServiceError.GENERIC_ENTITY_NOT_FOUND);
    }

    public static ServiceResult<ModmServiceStatus> entityDeleted(String entity) {
        return new ServiceResult<ModmServiceStatus>(true,
            new ModmServiceStatus(0, entity + " deleted."));
    }
}

The mapping:

public class RestUtils {
    private static final boolean PRETTY_PRINT = true;

    public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    public static final ObjectWriter OBJECT_WRITER = (PRETTY_PRINT ? OBJECT_MAPPER
        .writerWithDefaultPrettyPrinter() : OBJECT_MAPPER.writer());

    @SuppressWarnings("unchecked")
    public static <T> JacksonRepresentation<T> toJSON(T t) throws IOException {
        JsonRepresentation jsonRepresentation = null;
        JacksonRepresentation<T> jacksonRepresentation = null;
        jsonRepresentation = new JsonRepresentation(
            OBJECT_WRITER.writeValueAsString(t)); // Origin of incidental
                                                    // server error
        jacksonRepresentation = new JacksonRepresentation<T>(
            jsonRepresentation, (Class<T>) t.getClass());
        return jacksonRepresentation;
    }

    public static <T> Representation toRepresentation(ServiceResult<T> ss) {
        Representation representation = null;
        try {
        representation = RestUtils.toJSON(ss);
        } catch (IOException jsonException) {
            jsonException.printStackTrace();
            try {
                jsonException.printStackTrace();
                representation = RestUtils.toJSON(jsonException.getMessage());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return representation;
    }
}

The call:

RestUtils.toRepresentation(new ServiceResult<List<Group>>(groups));

The exception with stacktrace:

org.codehaus.jackson.map.JsonMappingException: (was java.lang.ArrayIndexOutOfBoundsException) (through reference chain: com.onior.modm.restlet.helpers.ServiceResult["success"])
    at org.codehaus.jackson.map.JsonMappingException.wrapWithPath(JsonMappingException.java:218)
    at org.codehaus.jackson.map.JsonMappingException.wrapWithPath(JsonMappingException.java:183)
    at org.codehaus.jackson.map.ser.std.SerializerBase.wrapAndThrow(SerializerBase.java:140)
    at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:158)
    at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112)
    at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:610)
    at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:256)
    at org.codehaus.jackson.map.ObjectWriter._configAndWriteValue(ObjectWriter.java:456)
    at org.codehaus.jackson.map.ObjectWriter.writeValueAsString(ObjectWriter.java:393)
    at com.onior.modm.restlet.helpers.RestUtils.toJSON(RestUtils.java:52)
    at com.onior.modm.restlet.helpers.RestUtils.toRepresentation(RestUtils.java:71)
    at com.onior.modm.restlet.resources.GroupCollectionResource.toJsonRead(GroupCollectionResource.java:191)
    at sun.reflect.GeneratedMethodAccessor143.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.restlet.resource.ServerResource.doHandle(ServerResource.java:506)
    at org.restlet.resource.ServerResource.get(ServerResource.java:707)
    at org.restlet.resource.ServerResource.doHandle(ServerResource.java:589)
    at org.restlet.resource.ServerResource.doNegotiatedHandle(ServerResource.java:649)
    at org.restlet.resource.ServerResource.doConditionalHandle(ServerResource.java:348)
    at org.restlet.resource.ServerResource.handle(ServerResource.java:952)
    at org.restlet.resource.Finder.handle(Finder.java:246)
    at org.restlet.routing.Filter.doHandle(Filter.java:159)
    at org.restlet.routing.Filter.handle(Filter.java:206)
    at org.restlet.routing.Router.doHandle(Router.java:431)
    at org.restlet.routing.Router.handle(Router.java:648)
    at org.restlet.routing.Filter.doHandle(Filter.java:159)
    at org.restlet.routing.Filter.handle(Filter.java:206)
    at org.restlet.routing.Filter.doHandle(Filter.java:159)
    at org.restlet.routing.Filter.handle(Filter.java:206)
    at org.restlet.routing.Router.doHandle(Router.java:431)
    at org.restlet.routing.Router.handle(Router.java:648)
    at org.restlet.routing.Filter.doHandle(Filter.java:159)
    at org.restlet.routing.Filter.handle(Filter.java:206)
    at org.restlet.routing.Filter.doHandle(Filter.java:159)
    at org.restlet.routing.Filter.handle(Filter.java:206)
    at org.restlet.routing.Filter.doHandle(Filter.java:159)
    at org.restlet.engine.application.StatusFilter.doHandle(StatusFilter.java:155)
    at org.restlet.routing.Filter.handle(Filter.java:206)
    at org.restlet.routing.Filter.doHandle(Filter.java:159)
    at org.restlet.routing.Filter.handle(Filter.java:206)
    at org.restlet.engine.CompositeHelper.handle(CompositeHelper.java:211)
    at org.restlet.engine.application.ApplicationHelper.handle(ApplicationHelper.java:84)
    at org.restlet.Application.handle(Application.java:381)
    at org.restlet.ext.servlet.ServletAdapter.service(ServletAdapter.java:206)
    at org.restlet.ext.spring.RestletFrameworkServlet.doService(RestletFrameworkServlet.java:124)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
    at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:861)
    at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:606)
    at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
    at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.ArrayIndexOutOfBoundsException
    at org.codehaus.jackson.impl.WriterBasedGenerator.writeRaw(WriterBasedGenerator.java:577)
    at org.codehaus.jackson.util.DefaultPrettyPrinter$Lf2SpacesIndenter.writeIndentation(DefaultPrettyPrinter.java:279)
    at org.codehaus.jackson.util.DefaultPrettyPrinter.beforeObjectEntries(DefaultPrettyPrinter.java:98)
    at org.codehaus.jackson.impl.WriterBasedGenerator._writePPFieldName(WriterBasedGenerator.java:410)
    at org.codehaus.jackson.impl.WriterBasedGenerator._writeFieldName(WriterBasedGenerator.java:340)
    at org.codehaus.jackson.impl.WriterBasedGenerator.writeFieldName(WriterBasedGenerator.java:217)
    at org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:444)
    at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:150)
    ... 58 more

1 Answer 1

2

Got the exact same ArrayIndexOutOfBoundsException with Jackson 1.9.9

After investigating, our root cause was a shared PrettyPrinter used by multiple threads. If you look in the Jackson code for the DefaultPrettyPrinter, you will see:

/**
 * Number of open levels of nesting. Used to determine amount of
 * indentation to use.
 */
protected int _nesting = 0;

_nesting ends up being used to access arrays. This variable is incremented and decremented when processing object. If multiple threads decrements it, it may end up negative causing the ArrayIndexOutOfBoundsException. Once negative, it will not be ever increased again because the exception will be generated before reaching a piece of code that would increment it.

In your code, I see you have a static object writer. You probably end up with a single instance of the DefaultPrettyPrinter. If your application can concurrently produce json object, given enough time, you will get the exception.

Stéphan

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for your analysis. I think ObjectMapper is considered to be thread-safe... so this is effectively a bug in Jackson 1.9?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.