Introduction: Why Every Java Developer Should Know This Trick
Imagine: you receive JSON from a server, containing dozens of similar objects. Clients, products, logs — it doesn't matter. You grab ObjectMapper, write readValue(json, MyClass[].class) — and it works. But a month later, the code crashes with a ClassCastException because someone replaced the array with a List. Sound familiar?
The question “How to use Jackson to deserialise an array of objects” on Stack Overflow has gathered tens of thousands of views. And this is no coincidence: deserializing an array of objects in Java is one of those tasks that seems simple until you encounter generics and Type Erasure. In 2025, Jackson remains the de facto standard for working with JSON, but best practices have changed. In this article, we will cover not only the basic approaches but also modern techniques that will save you headaches.
Basic Approach: Array or List?
The simplest way is to use an array. Jackson perfectly understands that MyClass[].class is an array of objects:
ObjectMapper mapper = new ObjectMapper();String json = "[{\\"id\\":1,\\"name\\":\\"Alice\\"},{\\"id\\":2,\\"name\\":\\"Bob\\"}]";MyClass[] array = mapper.readValue(json, MyClass[].class);Does it work? Yes. But this approach has drawbacks:
- Fixed-size array — inconvenient for dynamic data.
- No collection methods (
stream(),filter(),remove()). - Harder to extend if you later need a
List.
Therefore, in 90% of cases, you will want to get a List<MyClass>. And here's where the fun begins — Type Erasure in Java. If you write:
List<MyClass> list = mapper.readValue(json, List.class); // warning: uncheckedThe compiler will complain, and at runtime you will get a List<LinkedHashMap>, not a List<MyClass>. Generics are erased, and Jackson doesn't know what type to map the elements to.
TypeReference: The Correct Way for List
The solution is to use TypeReference. This is an anonymous inner class that preserves generic type information through reflection:
List<MyClass> list = mapper.readValue(json, new TypeReference<List<MyClass>>() {});Note the {} — this creates an anonymous class that captures the type. Jackson, via TypeFactory, understands that it needs to deserialize a List of MyClass. This approach is the gold standard in 2025.
When is TypeReference inconvenient?
- If you dynamically construct the type (e.g., from configuration).
- If you need to deserialize the same thing many times — creating an anonymous class each time is costly.
For these cases, there is an alternative — CollectionType.
CollectionType: The Power of JavaType
Jackson provides TypeFactory for constructing complex types:
JavaType type = mapper.getTypeFactory() .constructCollectionType(List.class, MyClass.class);List<MyClass> list = mapper.readValue(json, type);This method:
- Does not create anonymous classes (less overhead).
- Allows constructing types dynamically:
constructParametricType(Map.class, String.class, MyClass.class). - Ideal for libraries and frameworks where types are unknown at compile time.
Best practice 2025: use TypeReference for simple cases, CollectionType when flexibility or performance is needed.
Reader and InputStream: Don't Forget About Resources
Blogs
Book Recommendations
Read also
Microservices in JavaScript: Architecture and Practice with Node.js
Data Parsing and Scraping in Kotlin: From Beginner to Real Project
Kotlin vs Python for Backend: What to Choose in Practice
JavaScript and AI: How ChatGPT Became an Indispensable Developer Tool
Generics in Kotlin: Why They Are Needed and How to Use Them