ObjectMapper - Best practice for thread-safety and performance
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 ObjectReader
s 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.
As I mention in the comment, I have always used suggestion #1. I have no knowledge if there is difference between the options in terms of thread safety/performance or at all.
However, this approach will not work if the target type is itself parameterized with generic type. The most obvious usage is some collection:
Json.toObject1(List.class, str); // will deserialize into List<Object>
for this purpose you will have to use Jackson's TypeReference
// Suggestion 4:
public static <T> T toObject4(final TypeReference<T> typeRef, final String json) throws IOException {
return jsonMapper.readValue(json, typeRef);
}
Json.toObject4(new TypeReference<List<SomeClass>>(){}, str); // will deserialize into List<SomeClass>
About concurrency
ObjectMapper
versus ObjectReader
is not relevant here.
The ObjectReader
doesn't look to be helpful for your scenario.
Its specification says :
Builder object that can be used for per-serialization configuration of deserialization parameters, such as root type to use or object to update (instead of constructing new instance).
Note that both instances of ObjectMapper
and ObjectReader
are thread safe provided that their configuration is not changed between serialization/deserialization client calls.
The ObjectReader
specified indeed :
Mapper instances are fully thread-safe provided that ALL configuration of the instance occurs before ANY read or write calls.
While ObjectReader
has as difference to be immutable in the way where updating its configuration will make it return a new instance of as stated by its documentation :
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.
In your requirement, you don't want to change the configuration between client calls. So using ObjectMapper
looks more relevant.
So I would eliminate the 3) way and also the 2) way since jsonMapper.readerFor(type)
that is factory method for ObjectReader
instance. Still you don't matter to use an ObjectReader
here.
So the simplest and common way looks better :
// Suggestion 1:
public static <T> T toObject1(final Class<T> type, final String json) throws IOException {
return jsonMapper.readValue(json, type);
}
About performance
Besides, remember ObjectReader
is immutable. So the 2 and 3 ways create new instances of ObjectReader
at each call.
It doesn't look a good hint for performance.
Yes, these are lightweight objects but creating them at each time has a cost.
The ObjectReader
doc says :
Instances are initially constructed by ObjectMapper and can be reused, shared, cached; both because of thread-safety and because instances are relatively light-weight.
There you don't reuse these instances. So you lose any benefit in terms of caching and of performance.
You could store them into a Map
field and reuse them but do it only if you need to improve the actual performance of ObjectMapper
and of course measure before concluding anything.
Conclusion : for your use case, I think that performance as well as concurrency is better with the first solution (ObjectMapper
)