ObjectMapper - Best practice for thread-safety and performance

axeleration picture axeleration · Aug 27, 2019 · Viewed 9.6k times · Source

Summary

I want to find the best practice for using ObjectMapper and/or ObjectReader in terms of thread-safety and performance in the context of the use-case described below.

Background

I have a helper class (Json.java) where the method toObject() uses ObjectMapper to translate from a json string to an object of a given (json-mappable) class.

Problem / question

I have read that ObjectReader is often recommended for being fully thread-safe, but I mostly see it being in a non-generic context where the class to read for is predefined. In this context, what do you believe to be the best practice in terms of thread-safety and performance? In the code I have three suggestions that I could think of as a starting point.

I have tried to look at through the source code and docs for jackson-databind, but my theoretical Java skills are not good enough to derive an answer from them. I have also looked at similar questions on SO and elsewhere, but haven't found any that match my case closely enough.

import java.io.IOException;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;

public abstract class Json {

    private static final ObjectMapper jsonMapper = new ObjectMapper();
    
    // NOTE: jsonReader is only relevant for Suggestion 3.
    private static final ObjectReader jsonReader = jsonMapper.reader(); 

    // Suggestion 1:
    public static <T> T toObject1(final Class<T> type, final String json) throws IOException {
        return jsonMapper.readValue(json, type);
    }

    // Suggestion 2:
    public static <T> T toObject2(final Class<T> type, final String json) throws IOException {
        return jsonMapper.readerFor(type).readValue(json);
    }

    // Suggestion 3:
    public static <T> T toObject3(final Class<T> type, final String json) throws IOException {
        return jsonReader.forType(type).readValue(json);
    }

    // Remainder of class omitted for brevity.
}

Answer

Andrew Tobilko picture Andrew Tobilko · Aug 27, 2019
private static final ObjectMapper jsonMapper = new ObjectMapper();

Constructing an ObjectMapper instance is a relatively expensive operation, so it's recommended to create one object and reuse it. You did it right making it final.

// Suggestion 1:
public static <T> T toObject1(final Class<T> type, final String json) throws IOException {
    return jsonMapper.readValue(json, type);
}

You always read JSON to a POJO, so let's be precise and clear, and use ObjectReader.

// Suggestion 2:
public static <T> T toObject2(final Class<T> type, final String json) throws IOException {
    return jsonMapper.readerFor(type).readValue(json);
}

// Suggestion 3:
public static <T> T toObject3(final Class<T> type, final String json) throws IOException {
    return jsonReader.forType(type).readValue(json);
}

There is no difference, really. Both methods will construct a new ObjectReader object: the former (jsonMapper.readerFor(type)) will give you a fully-built instance directly, the latter (jsonReader.forType(type)) will complement the not-yet-usable jsonReader and returns a ready-to-use object. I would rather go with option 2 because I don't want to keep that field.

You shouldn't worry about performance or thread-safety. Even though creating an ObjectMapper might be costly (or making a copy out of it), getting and working with ObjectReaders is lightweight and completely thread-safe.

From the Java documentation (emphasis mine):

Uses "mutant factory" pattern so that instances are immutable (and thus fully thread-safe with no external synchronization); new instances are constructed for different configurations. Instances are initially constructed by ObjectMapper and can be reused, shared, cached; both because of thread-safety and because instances are relatively light-weight.

I recently had these questions myself and decided on ObjectMapper#reader(InjectableValues) as a factory method. It's very handy particularly when you want to customise an ObjectReader slightly or, as it was in my case, to adjust a DeserializationContext.

That's an excellent question, by the way.