JSON Parsing in Java: Jackson vs Gson Compared

Java has no built-in JSON support in its standard library, which is why two third-party libraries — Jackson and Gson — dominate the ecosystem. Both do the same basic job: converting JSON to Java objects and back. But their default behaviors diverge in ways that genuinely matter once you’re past a “hello world” example, and most comparisons of the two stop at syntax without actually testing what happens at the edges.

We compiled and ran every example in this guide to confirm actual behavior rather than describing it from memory — including a couple of differences between the two libraries that are easy to miss until they cause a bug.

If you’re new to JSON syntax itself, our JSON syntax guide covers the fundamentals this article builds on.

Setup

Jackson (via Maven):

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.14.0</version>
</dependency>

Gson (via Maven):

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.10.1</version>
</dependency>

Parsing JSON Into a Java Object

Both libraries can parse JSON directly into a plain Java object (POJO), matching JSON keys to field names automatically.

Jackson:

ObjectMapper mapper = new ObjectMapper();
Product p = mapper.readValue(
    "{\"name\":\"Widget\",\"price\":9.99,\"inStock\":true}",
    Product.class
);
// p.name = "Widget", p.price = 9.99, p.inStock = true

Gson:

Gson gson = new Gson();
Product p = gson.fromJson(
    "{\"name\":\"Widget\",\"price\":9.99,\"inStock\":true}",
    Product.class
);
// p.name = "Widget", p.price = 9.99, p.inStock = true

At this level, they’re nearly interchangeable — same result, similar amount of code. The differences show up once things go wrong or get more complex.

JSON Parsing in Java

Serializing a Java Object to JSON

Jackson:

String json = mapper.writeValueAsString(product);
// {"name":"Gadget","price":19.99,"inStock":false}

String pretty = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(product);

Gson:

String json = gson.toJson(product);
// {"name":"Gadget","price":19.99,"inStock":false}

Gson prettyGson = new GsonBuilder().setPrettyPrinting().create();
String pretty = prettyGson.toJson(product);

Both produce identical output here. Gson’s pretty-printing requires building a separate Gson instance with GsonBuilder, while Jackson’s is a one-off method call on the existing ObjectMapper — a minor ergonomic difference, not a functional one.

Real Difference #1: Unknown JSON Properties

This is the first place behavior actually diverges — and it’s not a minor detail. Given a Product class with only name, price, and inStock fields, what happens when the JSON includes an extra field the class doesn’t have?

String json = "{\"name\":\"Widget\",\"price\":9.99,\"inStock\":true,\"extra\":\"oops\"}";

Jackson’s default behavior: throws an exception.

Error class: UnrecognizedPropertyException

Gson’s default behavior: silently ignores the unknown field and parses successfully.

Unknown prop result: Widget (no error thrown)

This is a genuinely important difference to know about before you build something with either library. Jackson’s strictness catches typos and API drift early — if a field gets renamed upstream, you’ll know immediately. Gson’s leniency means your code keeps working even when the JSON shape changes, which is convenient until it silently masks a real problem. Jackson’s strict behavior can be turned off with mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) if you want Gson-like leniency; Gson doesn’t offer the reverse (strict-by-default) option built in.

Real Difference #2: Number Types When Parsing Into a Map

This one is subtle enough that it can cause real bugs if you’re not aware of it. When parsing JSON into a generic Map<String, Object> rather than a specific class, what type does a whole number end up as?

Map<String, Object> map = mapper.readValue("{\"version\":2}", Map.class); // Jackson
// map.get("version") is an Integer

Map<?, ?> map = gson.fromJson("{\"version\":2}", Map.class); // Gson
// map.get("version") is a Double

Jackson preserves 2 as an Integer. Gson converts it to a Double (2.0), because Gson’s default number handling for untyped destinations always uses Double. If your code does something like if (map.get("version") instanceof Integer), that check will pass with Jackson and silently fail with Gson — a real source of confusing bugs when switching libraries or working with both in the same codebase.

Error Handling and Messages

Both libraries throw exceptions on invalid JSON, but with different exception types and message formats.

Jackson, given trailing-comma JSON:

Error class: JsonParseException
Location: line 1, col 39

Gson, given the same invalid JSON:

Error class: JsonSyntaxException
Message: com.google.gson.stream.MalformedJsonException: Expected name at line 1 column 39 path $.

Both correctly report the line and column — genuinely useful for tracking down exactly where a large JSON payload broke, the same detail our JSON Formatter and Validator shows visually rather than as a stack trace. Gson’s message additionally includes a path ($. here), showing the JSON path where parsing failed, which can help when the error is deep inside nested structures.

Working With JSON Without a Predefined Class

Sometimes you don’t have — or don’t want — a POJO for the JSON you’re handling. Both libraries support tree-style navigation for this case.

Jackson, using JsonNode:

JsonNode root = mapper.readTree(json);
String name = root.path("user").path("name").asText();
String firstTag = root.path("user").path("tags").get(0).asText();

Gson, using JsonObject:

JsonObject root = JsonParser.parseString(json).getAsJsonObject();
String name = root.getAsJsonObject("user").get("name").getAsString();
String firstTag = root.getAsJsonObject("user").getAsJsonArray("tags").get(0).getAsString();

Jackson’s .path() method is notably safer for missing keys — it returns a “missing” node instead of throwing, letting you chain calls without a null check at every step. Gson’s .get() calls will throw or return null if a key doesn’t exist, requiring more defensive code for deeply nested, possibly-incomplete JSON.

Missing Fields

If the JSON is missing a field the POJO expects, both libraries handle it the same way — no error, the field just gets its type’s default value:

Product p = gson.fromJson("{\"name\":\"Widget\"}", Product.class);
// p.price = 0.0, p.inStock = false — no exception from either library

This is one area where Jackson and Gson agree: neither treats a missing field as an error by default, only Jackson’s unknown extra field behavior differs, not missing expected fields.

Which Should You Choose?

  • Choose Jackson if you want strict validation by default, safer null-tolerant tree navigation, and you’re already in a Spring-based project — Jackson is Spring Boot’s default JSON library, so it’s likely already on your classpath.
  • Choose Gson if you want simpler setup, more lenient parsing that tolerates JSON shape drift, and a smaller dependency footprint for a project that doesn’t already pull in Jackson transitively.

Neither is objectively “better” — the unknown-property and number-typing differences above are the kind of thing that should actually influence the decision, rather than picking based on general popularity.

Frequently Asked Questions

Can I make Gson strict about unknown properties, like Jackson’s default?

Not directly through a built-in flag the way Jackson offers FAIL_ON_UNKNOWN_PROPERTIES. Achieving Jackson-like strictness in Gson generally requires writing a custom TypeAdapter or validating the parsed object’s fields manually afterward.

Does either library handle JSON Schema validation?

No — both Jackson and Gson handle parsing and serialization, not schema validation (checking that JSON matches a required structure with specific field types and constraints). For that, a separate library like everit-org/json-schema or networknt/json-schema-validator is needed on top of either.

Which library is used by default in Spring Boot?

Jackson. If you’re working in a Spring Boot project, Jackson is almost certainly already on your classpath as a transitive dependency, which is often the deciding factor in practice more than a feature-by-feature comparison.

Can Jackson and Gson coexist in the same project?

Yes, there’s no conflict running both — some teams do this deliberately, using Gson for lightweight internal tooling and Jackson for stricter API-facing serialization. Just be aware of the number-typing difference above if the same JSON data passes through both at different points.

Do both libraries support Java records (Java 14+)?

Yes, both Jackson (2.12+) and Gson (2.10+) support deserializing into Java records, though Jackson’s record support integrates more smoothly with its existing annotation system if you need custom field mapping.

Summary

Jackson and Gson produce identical results for straightforward parsing and serialization, but diverge in ways that matter once JSON shapes drift or edge cases appear: Jackson fails loudly on unknown fields and preserves number types more predictably in untyped contexts, while Gson stays lenient and defaults untyped numbers to Double. Both give you accurate line/column error reporting, and both handle missing fields identically by falling back to default values rather than throwing.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *