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.
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.
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.
}
Jackson's ObjectMapper is completely thread safe and should not be re-instantiated every time #2170.
Creating an ObjectMapper is quite expensive. Therefore it is recommended that you reuse your ObjectMapper instance. The Jackon ObjectMapper is thread-safe so it can safely be reused.
Yes, that is safe and recommended.
ObjectMapper provides functionality for reading and writing JSON, either to and from basic POJOs (Plain Old Java Objects), or to and from a general-purpose JSON Tree Model ( JsonNode ), as well as related functionality for performing conversions.
Although ObjectMapper is thread safe, I would strongly discourage from declaring it as a static variable, especially in multithreaded application. Not even because it is a bad practice, but because you are running a heavy risk of deadlocking. I am telling it from my own experience.
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?
Although it is safe to declare a static ObjectMapper in terms of thread safety, you should be aware that constructing static Object variables in Java is considered bad practice. For more details, see Why are static variables considered evil? (and if you'd like, my answer)
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 :
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.
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
)
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With