Introduction
Gson is Google's Java library for converting between JSON and Java objects. To deserialize a JSON array into a typed ArrayList<T>, you need TypeToken because Java's type erasure removes generic type information at runtime. Without TypeToken, Gson cannot determine that you want an ArrayList<Person> instead of an ArrayList<Object>. The pattern is: create a TypeToken capturing the full generic type, pass its getType() to gson.fromJson(), and Gson correctly deserializes each array element into the specified type.
Basic Deserialization with TypeToken
1import com.google.gson.Gson;
2import com.google.gson.reflect.TypeToken;
3import java.lang.reflect.Type;
4import java.util.ArrayList;
5import java.util.List;
6
7public class Main {
8 public static void main(String[] args) {
9 Gson gson = new Gson();
10
11 String json = "[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]";
12
13 // TypeToken captures the generic type ArrayList<Person>
14 Type listType = new TypeToken<ArrayList<Person>>(){}.getType();
15 ArrayList<Person> people = gson.fromJson(json, listType);
16
17 for (Person p : people) {
18 System.out.println(p.getName() + " is " + p.getAge());
19 }
20 // Alice is 30
21 // Bob is 25
22 }
23}
24
25class Person {
26 private String name;
27 private int age;
28
29 // Getters and setters
30 public String getName() { return name; }
31 public void setName(String name) { this.name = name; }
32 public int getAge() { return age; }
33 public void setAge(int age) { this.age = age; }
34}
The new TypeToken<ArrayList<Person>>(){} syntax creates an anonymous subclass that preserves the generic type through reflection — working around Java's type erasure.
Why TypeToken Is Needed
1Gson gson = new Gson();
2String json = "[{\"name\":\"Alice\"},{\"name\":\"Bob\"}]";
3
4// WRONG — returns ArrayList<LinkedTreeMap>, not ArrayList<Person>
5ArrayList list = gson.fromJson(json, ArrayList.class);
6System.out.println(list.get(0).getClass());
7// com.google.gson.internal.LinkedTreeMap
8
9// CORRECT — returns ArrayList<Person>
10Type type = new TypeToken<ArrayList<Person>>(){}.getType();
11ArrayList<Person> people = gson.fromJson(json, type);
12System.out.println(people.get(0).getClass());
13// Person
Without TypeToken, Gson does not know the element type and defaults to its internal map representation.
Different Collection Types
1Gson gson = new Gson();
2String json = "[1, 2, 3, 4, 5]";
3
4// ArrayList<Integer>
5Type intListType = new TypeToken<ArrayList<Integer>>(){}.getType();
6ArrayList<Integer> intList = gson.fromJson(json, intListType);
7
8// LinkedList<Integer>
9Type linkedType = new TypeToken<LinkedList<Integer>>(){}.getType();
10LinkedList<Integer> linkedList = gson.fromJson(json, linkedType);
11
12// List<Integer> (interface — Gson uses ArrayList)
13Type listType = new TypeToken<List<Integer>>(){}.getType();
14List<Integer> list = gson.fromJson(json, listType);
15
16// Set<Integer> (removes duplicates)
17String jsonDups = "[1, 2, 2, 3, 3, 3]";
18Type setType = new TypeToken<HashSet<Integer>>(){}.getType();
19Set<Integer> set = gson.fromJson(jsonDups, setType);
20System.out.println(set); // [1, 2, 3]
Nested Generic Types
1// List of lists
2String json = "[[1,2,3],[4,5,6],[7,8,9]]";
3Type nestedType = new TypeToken<ArrayList<ArrayList<Integer>>>(){}.getType();
4ArrayList<ArrayList<Integer>> matrix = gson.fromJson(json, nestedType);
5System.out.println(matrix.get(1).get(2)); // 6
6
7// Map inside a list
8String mapJson = "[{\"key\":\"a\",\"value\":1},{\"key\":\"b\",\"value\":2}]";
9Type mapListType = new TypeToken<ArrayList<Map<String, Object>>>(){}.getType();
10ArrayList<Map<String, Object>> mapList = gson.fromJson(mapJson, mapListType);
Serialization (ArrayList to JSON)
1Gson gson = new Gson();
2
3ArrayList<Person> people = new ArrayList<>();
4people.add(new Person("Alice", 30));
5people.add(new Person("Bob", 25));
6
7// Serialization does not need TypeToken
8String json = gson.toJson(people);
9System.out.println(json);
10// [{"name":"Alice","age":30},{"name":"Bob","age":25}]
11
12// Pretty printing
13Gson prettyGson = new GsonBuilder().setPrettyPrinting().create();
14System.out.println(prettyGson.toJson(people));
Generic Helper Method
1public class JsonHelper {
2 private static final Gson gson = new Gson();
3
4 public static <T> ArrayList<T> fromJsonArray(String json, Class<T> clazz) {
5 Type type = TypeToken.getParameterized(ArrayList.class, clazz).getType();
6 return gson.fromJson(json, type);
7 }
8
9 public static <T> String toJson(List<T> list) {
10 return gson.toJson(list);
11 }
12}
13
14// Usage
15ArrayList<Person> people = JsonHelper.fromJsonArray(json, Person.class);
16ArrayList<Integer> numbers = JsonHelper.fromJsonArray("[1,2,3]", Integer.class);
TypeToken.getParameterized() (Gson 2.8+) creates the type programmatically, avoiding the anonymous subclass syntax.
Custom Deserializer
1import com.google.gson.*;
2import java.lang.reflect.Type;
3
4public class PersonDeserializer implements JsonDeserializer<Person> {
5 @Override
6 public Person deserialize(JsonElement json, Type typeOfT,
7 JsonDeserializationContext context) throws JsonParseException {
8 JsonObject obj = json.getAsJsonObject();
9 String name = obj.get("name").getAsString();
10 int age = obj.has("age") ? obj.get("age").getAsInt() : 0;
11 return new Person(name, age);
12 }
13}
14
15// Register
16Gson gson = new GsonBuilder()
17 .registerTypeAdapter(Person.class, new PersonDeserializer())
18 .create();
19
20Type listType = new TypeToken<ArrayList<Person>>(){}.getType();
21ArrayList<Person> people = gson.fromJson(json, listType);
Handling null and Empty Arrays
1Gson gson = new Gson();
2Type type = new TypeToken<ArrayList<Person>>(){}.getType();
3
4// Empty array
5ArrayList<Person> empty = gson.fromJson("[]", type);
6System.out.println(empty.size()); // 0
7
8// null JSON
9ArrayList<Person> nullResult = gson.fromJson("null", type);
10System.out.println(nullResult); // null
11
12// Safe handling
13ArrayList<Person> safe = gson.fromJson(json, type);
14if (safe == null) safe = new ArrayList<>();
Common Pitfalls
Omitting TypeToken and getting LinkedTreeMap instead of your POJO: Without TypeToken, gson.fromJson(json, ArrayList.class) deserializes objects as LinkedTreeMap, not your target class. Always use new TypeToken<ArrayList<YourClass>>(){}.getType().
Reusing a raw Type variable across different generic types: Each TypeToken is specific to one parameterized type. Creating new TypeToken<ArrayList<Person>>(){}.getType() and using it for ArrayList<Order> silently produces wrong results.
Missing a no-arg constructor on the target class: Gson creates instances using the no-arg constructor by default. If Person only has a parameterized constructor, deserialization fails with RuntimeException. Add a no-arg constructor or register a custom InstanceCreator.
Field name mismatches between JSON and Java: Gson maps JSON keys to Java field names by exact match. If JSON uses "first_name" but the Java field is firstName, the field is null. Use @SerializedName("first_name") to map the names.
Not handling null values in JSON arrays: JSON arrays can contain null elements ([{"name":"Alice"},null,{"name":"Bob"}]). Iterating without null checks causes NullPointerException. Filter or check for null elements after deserialization.
Summary
Use new TypeToken<ArrayList<T>>(){}.getType() to capture the generic type for Gson deserialization
Without TypeToken, Gson deserializes JSON objects as LinkedTreeMap instead of your target class
Use TypeToken.getParameterized(ArrayList.class, MyClass.class) (Gson 2.8+) for programmatic type creation
Serialization (toJson) does not need TypeToken — Gson inspects the runtime types automatically
Always provide a no-arg constructor on target classes and use @SerializedName for JSON key mapping