Get started with the JSON Binding API, Part 4

Is it time for a JSON binding standard?

Comparing Gson, Jackson, and JSON-B highlights inconsistencies in basic features and behavior


Content series:

This content is part # of 4 in the series: Get started with the JSON Binding API, Part 4

Stay tuned for additional content in this series.

This content is part of the series:Get started with the JSON Binding API, Part 4

Stay tuned for additional content in this series.

JSON has been the data interchange format of choice for nearly a decade, but until now we have not had a specification robust enough to standardize on. With the release of the JSON Binding API, vendors have the opportunity to standardize both default and custom binding operations in JSON parsers. As developers, we should insist that they do.

Comparing serialization and deserialization behavior across three JSON parsers brings to light the differences and idiosyncrasies in how these tools handle normal binding operations. It also becomes clear that numerous key features are inconsistently supported, with some tools offering them while others do not.

While there is value in uniqueness, that should not come at the expense of functionality. Allowing default behavior to differ so substantially between implementations may influence the choice of tool more than differences in performance, which should be the primary deciding factor.

The case for a JSON binding standard

Building an effective standard takes time, and works best when it’s based on established use cases and best practices. A good standard is the product of domain knowledge based on years of experimentation, correction, and innovation. In the case of JSON parsers, I believe the time is ripe for a standard.

Standardizing JSON binding operations and features would enable developers to compare tools based on the efficiency and performance of the underlying engine. Implementers would be freed to focus more on performance than features. The overall quality of tools would improve

Standardization does not stifle innovation; instead, it reflects the innovation that has already occurred. It provides a sound base for future innovation and advancements, and the best additions become candidates for incorporation into the standard.

I believe the case is strong for standardizing, and with the JSON Binding API specification there is no good reason not to do it. In the following sections I will compare core features among three JSON binding tools. While the comparisons are interesting, my main point is to show the discrepancy in how the tools handle binding operations and type formats in Java™.

Comparing JSON parsers

This article compares two popular frameworks—FastXML's Jackson Project and Google's Gson—with the JSON Binding API.

We’ll start by comparing the frameworks’ default treatment of key data types in structured JSON documents. We’ll then look at how each framework handles customization for those data types, ranging from basic Java types to the new date and time formats found in Java 8.

You’ll see for yourself how each JSON framework behaves when serializing and deserializing Java objects to and from JSON. My goal isn’t a scientific review or benchmark comparison, but an overview of the differences and consistencies between the frameworks. Ultimately, I am most interested in demonstrating the benefits of standardizing on the JSON Binding specification.

For the examples in this article I’ve used the JSON Binding API version 1.0 with JSON Processing 1.1 as the provider, Gson version 2.8.2, and Jackson version 2.9.2. I’ve also created a GitHub repository containing code examples and unit tests so that you can duplicate the examples for yourself.

Binding models

In a JSON parser, the binding model is the mechanism of serialization and deserialization. All three of the frameworks have default binding behavior for common binding scenarios, and each one offers custom binding solutions. For custom binding, developers have the option to use compile-time annotations or runtime configurations. In this section I show how JSON-B, Jackson, and Gson approach custom binding through runtime configuration. The models are quite different, so this is a good starting point for comparison. Later sections will explore customization features in detail.

Runtime binding with JSON-B

Developers access JSON Binding’s runtime configuration via the JsonConfig class. You customize runtime configuration by calling with(...) methods and passing in configuration properties. For instance, new JsonConfig.withPropertyOrderStrategy(PropertyOrderStrategy.REVERSE).

The JsonConfig instance is then set on the JsonBuilder instance on which the toJson() or fromJson() methods are called. Listing 1 shows an example.

Listing 1. Runtime configuration for the JSON Binding API
JsonbConfig jsonbConfig = new JsonbConfig()

String json = JsonbBuilder

AnObject anObj = JsonbBuilder
    .fromJson("{JSON}", AnObject.class);

Runtime binding with Jackson

Jackson’s runtime model differs from JSON-B’s in that you configure the object mapper engine directly. This engine is then used to perform serialization and deserialization on the target object. As you can see in Listing 2, the ObjectMapper provides a substantial range of configuration methods.

Listing 2. Jackson’s runtime model
ObjectMapper objectMapper = new ObjectMapper()

String json = objectMapper

AnObject anObj = objectMapper
    .readValue("{JSON}", AnObject.class);

Runtime binding with Gson

Gson configures an instance of the GsonBuilder from which a Gson instance is created. A Gson object provides methods for operations of serialization and deserialization. You can see the Gson runtime model in Listing 3.

Listing 3. The Gson runtime model
GsonBuilder gsonBuilder = new GsonBuilder()

String json = gsonBuilder
AnObject anObj = gsonBuilder
    .fromJson("{JSON}", AnObject.class);

Next we’ll look at how the three parsers handle types, starting with basic Java types.

Basic Java types

In Java, basic types refer to all the primitive types and their respective wrapper classes, along with the String type. We’ll compare a simple example where all of these types are serialized to JSON.

To start, the class in Listing 4 contains all the basic types for the Java language.

Listing 4. Basic Java type
public class AllBasicTypes {

    // Primitive types
    private byte bytePrimitive;
    private short shortPrimitive;
    private char charPrimitive;
    private int intPrimitive;
    private long longPrimitive;
    private float floatPrimitive;
    private double doublePrimitive;
    private boolean aBoolean;

    // Wrapper types
    private Byte byteWrapper = 0;
    private Short shortWrapper = 0;
    private Character charWrapper = 0;
    private Integer intWrapper = 0;
    private Long longWrapper = 0L;
    private Float floatWrapper = 0F;
    private Double doubleWrapper = 0D;
    private Boolean booleanWrapper = false;

    private String string = "Hello World";

    // Getters and setter omitted for brevity

Serialization with all three frameworks results in the JSON document shown in Listing 5. This serialization behavior, as you will see, is currently the only behavior that is consistent across frameworks. Note that fields are self describing in order to make matching the JSON properties to the source class easier.

Listing 5. JSON document
    "bytePrimitive": 0,
    "shortPrimitive": 0,
    "charPrimitive": "\u0000",
    "intPrimitive": 0,
    "longPrimitive": 0,
    "floatPrimitive": 0,
    "doublePrimitive": 0,
    "aBoolean": false,
    "byteWrapper": 0,
    "shortWrapper": 0,
    "charWrapper": "\u0000",
    "intWrapper": 0,
    "longWrapper": 0,
    "floatWrapper": 0,
    "doubleWrapper": 0,
    "booleanWrapper": false,
    "string": "Hello World"

All three of the JSON parsers use default configurations for operating on the basic types, with few configuration options. (See Get started with the JSON Binding API, Part 3 for a demonstration of number formatting with JSON-B.)

Specific Java types

So-called specific types include more complex JDK types such as those that extend java.lang.Number. Among these are BigDecimal, AtomicInteger, and LongAdder; Optional types including OptionalInt and Optional<type>; and the URL and URI instances.

First let’s compare Number types. All JSON frameworks perform serialization by simply extracting the internal number as the property value. The treatment of BigDecimal, BigInteger, AtomicInteger, and LongInteger is consistent across the three frameworks.

The URI and URL classes are also treated the same way across all three frameworks: after serialization, the internal value is expressed as a String in the resulting JSON document.

Optional types are not consistently treated, however. Table 1 shows the results of serializing the two instances in Listing 6.

Listing 6. Optional test class
Optional<String> stringOptional = Optional.of("Hello World");
OptionalInt optionalInt = OptionalInt.of(10);

In Table 1, I’ve separated the two fields to make comparison easier.

Table 1. Optional types
JSON-B"stringOptional": "Hello World""optionalInt": 10
Jackson"stringOptional": { "present": true }"optionalInt": { "asInt": 10,"present": true }
Gson"stringOptional": { "value": "Hello World" }"optionalInt": { "isPresent": true, "value": 10 }

As you see, JSON-B fully supports Optional values by peeking inside and retrieving the internal value. Neither Jackson or Gson support Optionals, out-of-the-box, and the serialization of a String Optional is the least consistent.

Gson serializes the structure of the Optional to a JSON object containing the String, while Jackson does not even include the value of the underlying instance; instead it only states that it is present.

While OptionalInt and the rest of the Optional number family are not well supported in Gson or Jackson, they are at least serialized to a sensible JSON object that includes the underlying value and the boolean representing that the value is present.

Date, time, and calendar types

Dates, times, and calendar type are numerous, including both the old java.util.Date and java.util.Calendar types and the new Java 8 date and time classes found in java.time.

Given the extensive range of types to compare I have chosen just three:

  • An instance of Calendar with the date set using the builder.
  • An instance of Date with the date set from a String and applying the SimpleDateFormat.
  • An instance Java 8's LocalDate with the date set using the parse() method.

The Calendar type

The Calendar instance is configured with the date of December 25, 2017, as shown in Listing 7.

Listing 7. Calendar type
new Calendar.Builder().setDate(2017, 1, 25).build()

Table 2 shows the results of serializing this instance using each of the three frameworks.

Table 2. Calendar instance serialized
Calendar instance
    "year": 2017,
    "month": 11,
    "dayOfMonth": 25,
    "hourOfDay": 0,
    "minute": 0,
    "second": 0

It is clear from Table 2 that each framework implements a different methodology to serialize a Calendar instance. JSON-B outputs the date with a zero time and timezone. Jackson produces the number of milliseconds since the epoch (January 1, 1970) and Gson produces a JSON object containing the numerical parts of the date.

The difference in the default behavior is stark. While it’s possible to configuration these formats to something more readable, there really shouldn’t be such a range of difference in the output format.

The Date and LocalDate types

Now let’s see how Date and LocalDate are treated. Listing 8 shows the code for creating these two instances and Table 3 shows their serialization.

Listing 8. LocalDate and Date instances
new SimpleDateFormat("dd/MM/yyyy").parse("25/12/2017");
Table 3. Date and LocalDate instances serialized
  "year": 2017,
  "month": "DECEMBER",
  "chronology": {
    "id": "ISO",
    "calendarType": "iso8601"
  "dayOfMonth": 25,
  "dayOfWeek": "MONDAY",
  "dayOfYear": 359,
  "leapYear": false,
  "monthValue": 12,
  "era": "CE"
GsonDec 25, 2017 12:00:00 AM
  "year": 2017,
  "month": 12,
  "day": 25

The Date type

JSON Binding ignores the original date format and applies the default formatting style, adding time and timezone information. Jackson returns the milliseconds since the epoch, and Gson applies the MEDIUM date/time style.

The LocalDate type

A LocalDate instance is treated differently, and from the perspective of JSON-B a little more logically than Date. JSON-B outputs just the date in the default format, with no time or time zone information. Jackson produces a JSON object by calling the accessor methods in the instance, effectively treating LocalDate as a POJO. Gson constructs an object with three properties for year, month, and day.

Clearly the Date, LocalDate, and Calendar instances are not treated consistently across the three JSON frameworks.

Configuring date and time formats

There are both compile-time and runtime customizations for date and time formats. Only JSON-B and Jackson provide annotations that specify a date and time format for a given property, method, or class, as shown in Listings 9 and 10.

Listing 9. Configuring the date format at compile time with JSON Binding
@JsonbDateFormat(value = "MM/dd/yyyy", locale = "Locale.ENGLISH")
Listing 10. Configuring the date format at compile time with Jackson
@JsonFormat(pattern = "MM/dd/yyyy", locale = "Locale.ENGLISH")

All three parsers have runtime configurations that set the format globally, as shown in Listings 11, 12, and 13.

Listing 11. Configuring the date format at runtime with JSON Binding
new JsonbConfig().withDateFormat("MM/dd/yyyy", Locale.ENGLISH)
Listing 12. Configuring the date format at runtime with Jackson
new ObjectMapper().setDateFormat(new SimpleDateFormat("MM/dd/yyyy"))
Listing 13. Configuring the date format at runtime with Gson
new GsonBuilder().setDateFormat("MM/dd/yyyy")

Arrays, collections, and maps

The parsers are surprisingly consistent in their treatment of arrays, collections, and maps. The reason is that each of these structures maps directly to its equivalent JSON type. So the code in Listing 14 serializes to the JSON document in Listing 15 regardless of which JSON framework is in use.

Listing 14. Sample array, collection, and map
private int[] intArray = new int[]{ 1, 2, 3, 4 };
private String[] stringArray = new String[]{ "one", "two" };

Collection<Object> objectCollection = new ArrayList<Object>() {{

private Map<String, Integer> stringIntegerMap = new HashMap<String, Integer>() {{
   put("one", 1);
   put("two", 2);

Here is the JSON document.

Listing 15. The serialized array, collection, and map
    "intArray": [1, 2, 3, 4],
    "objectCollection": ["one", "two"], 
    "stringArray": ["one", "two"],
    "stringIntegerMap": { "one": 1, "two": 2 }

Nulls, nulls in collections, and Optional.empty()

Nulls are not serialized by JSON Binding or Gson, but they are preserved by Jackson. On deserialization the absence of a value in a JSON document does not result in a call to the corresponding setter method in the target object. If the value is null, however, then it is set as a normal value.

For all three frameworks nulls are preserved in arrays, maps, and collections by default.


The Optional.empty() value represents a nonexistent value and thus JSON Binding treats it similarly to null: the JSON document will not include the property on serialization. Both Jackson and Gson will attempt to serialize this value to a JSON object, however: Jackson treats Optional.empty() as a POJO, while Gson produces an empty JSON Object.

Table 4 summarizes the serialization of Optional<Object> emptyOptional = Optional.empty().

Table 4. Serialization of an Optional.Empty() instance
"Property not included in JSON document."
  "emptyOptional": {
      "present": false
   "emptyOptional": {}

Configuration options for null

The JSON Binding API provides both runtime and compile-time configuration options for nulls. For a compile-time configuration to include a null for a specific field, you could use the @JsonbProperty annotation and set the nillable flag to true. Alternatively, you could globally set inclusion at the class or package level with the @JsonbNillable annotation, as shown in Listing 16.

Listing 16. JSON Binding compile-time null configurations
public class CompileTimeSampler {
   @JsonbProperty(nillable = true)
   private String nillable;
   // field level configuration overrides class and package level configuration settings.

For a runtime configuration you would use the method .withNullValues() set to true, as shown in Listing 17.

Listing 17. JSON Binding's runtime null configuration
new JsonbConfig().withNullValues(true);

Jackson also provides both runtime and compile-time options for excluding null values. Listing 18 shows how to use the @JsonInclude annotation to exclude null values.

Listing 18. A Jackson compile-time annotation to exclude nulls

Jackson offers two runtime configuration options. One allows you to exclude nulls globally while the other supports exclusion from within Maps (although depreciated) as shown in Listing 19.

Listing 19. A Jackson runtime configuration to exclude nulls
new ObjectMapper()
    .configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);

Gson’s runtime null configuration globally switches on serialization with nulls preserved, as shown in Listing 20.

Listing 20. A Gson runtime configuration to exclude nulls
new GsonBuilder().serializeNulls();

Once again you see that type serialization is treated inconsistently across the three frameworks. Not only are nulls treated inconsistently, but even the configuration options differ.

Field visibility

A field’s visibility determines whether or not it will be serialized to the JSON document and deserialized to the field of the target instance. Additionally, each framework interprets visibility differently.

The code in Listing 21 was serialized using all three frameworks. The fields are self describing. Some fields have corresponding setter/getter methods (where the field name suggests this), while the remaining fields do not. Additionally, one virtual field has no corresponding field and is represented by only a getter method.

Listing 21. Class containing field visibility options
public class FieldsVisibility {

    // Without getter and setters
    private String privateString = "";
    String defaultString = "";     
    protected String protectedString = "";
    public String publicString = "";
    final private String finalPrivateString = "";
    final public String finalPublicString = "";
    static public String STATIC_PUBLIC_STRING = "";

    // With getter and setters. Omitted for brevity
    private String privateStringWithSetterGetter = "";
    String defaultStringWithSetterGetter = "";
    protected String protectedStringWithSetterGetter = "";
    public String publicStringWithSetterGetter = "";
    final private String finalPrivateStringWithSetterGetter = "";
    final public String finalPublicStringWithSetterGetter = "";
    static public String STATIC_PUBLIC_STRING_WITH_SETTER_GETTER = "";

    public String getVirtualField() {
        return "";

So that we can compare how each framework approaches field visibility, Table 5 presents the outcomes of serializing the code in Listing 21 and compiling the results.

Table 5. Summary of each framework’s default handling of field visibility
Access Via...Field ModifierJSON-BJacksonGson
final publicNoNoYes
final privateNoNoNo
static publicYesYesYes
Public GetterprivateYesYesYes
final privateYesYesYes
final publicYesYesYes
static publicNoNoNo
virtual fieldYesYesNo

The only consistency is that all three frameworks respect public non-static access modifiers. Additionally, none of the frameworks ever serializes public static fields, even if the field has a public getter method.

It is clear that Gson does not respect a field's access modifier at all and includes all fields in the serialization, regardless of the modifier specified (except public static fields). Gson also does not include virtual fields, which both JSON-B and Jackson do.

JSON-B and Jackson are identical in their approach to field visibility. Gson differs by including final private fields and not including virtual fields.

Visibility configuration

Visibility configuration options are quite extensive and can be complex. Let’s look at how each framework configures visibility.

JSON Binding

JSON-B provides a simple @JsonbTransient annotation that excludes any annotated field from serialization and deserialization.

In JSON-B there is a more sophisticated way to control field visibility. You can create a custom visibility strategy by implementing the PropertyVisibilityStrategy interface and setting it as a runtime property on the JsonbConfig instance. Listing 22 demonstrates. (For an example implementation of the PropertyVisibilityStrategy class, see Part 3.)

Listing 22. Setting a custom visibility strategy in JSON-B
new JsonbConfig()
    .withPropertyVisibilityStrategy(new CustomPropertyVisibilityStrategy());


Jackson provides compile-time annotations that ignore any field annotated with @JsonIgnore, along with any field specifically mentioned in the list passed to the @JsonIgnoreProperties({"aField"}) class-level annotation. Method and field visibility can be configured with the @JsonAutoDetect, as shown in Listing 23.

Listing 23. Setting field visibility with Jackson annotations
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC)
public class CompileTimeSampler {
    // fields and methods omitted for brevity

Jackson also offers a runtime configuration that allows the explicit inclusion of fields based on their access modifier. The code example in Listing 24 shows the inclusion of public and protected fields during serialization.

Listing 24. Configure public and protected fields for inclusion
new ObjectMapper()


Gson provides a compile-time annotation for specifying fields to include in serialization and deserialization operations. In this case, you mark each field with the @Expose annotation and set the serialize and/or deserialize flag to true or false, as shown in Listing 25.

Listing 25. Using the @Expose annotation
@Expose(serialize = false, deserialize = false)
private String aField;

In order to use this annotation you must enable it by calling the excludeFieldsWithoutExposeAnnotation() method on the GsonBuilder instance, as shown in Listing 26.

Listing 26. Enabling the @Expose annotation
new GsonBuilder().excludeFieldsWithoutExposeAnnotation();

Alternatively, fields can be explicitly excluded based on the fields access modifier as shown in Listing 27.

Listing 27. Exclude fields based on its modifier
new GsonBuilder().excludeFieldsWithModifiers(Modifier.PROTECTED);

Configuring property order

The IETF RFC 7159 JSON interchange format specification defines a JSON object as an "unordered collection of zero or more name/value pairs". In some cases, however, it is required that properties be displayed in a given order. By default, JSON-B orders properties lexicographically, while Jackson and Gson use the order in which the fields appear in the class.

In all three frameworks, you are given the capability to specify property order by explicitly listing field names or by specifying an order strategy.

JSON Binding

JSON-B provides both a runtime and compile-time mechanism for specifying property order. The order is specified at runtime by passing a list of fields to the @JsonbPropertyOrder annotation, as shown in Listing 28.

Listing 28. Specifying field order explicitly in JSON-B
@JsonbPropertyOrder({"firstName", "title", "author"})

For runtime configuration, you set a global order strategy by specifying the desired order strategy on an instance of JsonbConfig, as shown in Listing 29.

Listing 29. Specifying order reverse lexicographically in JSON-B
new JsonbConfig()


Jackson also provides a way to explicitly state the order of fields, as well as identifying the order as alphabetic, as shown in Listing 30.

Listing 30. Two ways to specify field order in Jackson
@JsonPropertyOrder(value = {"firstName", "title", "author"}, alphabetic = true)

@JsonPropertyOrder(alphabetic = true)

You also can use runtime configurations to specify property order globally, as shown in Listing 31.

Listing 31. Specifying the property order globally in Jackson
new ObjectMapper().configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);


Gson does not provide configuration settings that easily order properties. Instead you must create a custom serializer/deserializer that encapsulate logic that orders the fields.


JSON-B approaches inheritance by placing the parent class first, followed by the child class. Listing 32, shows code for a Parent class, and Listing 33, shows code for the Child class extending that Parent class.

Listing 32. Parent class
public class Parent {
   private String parentName = "Parent";
   // Getters/setter omitted
Listing 33. Child class extending Parent
public class Child extends Parent {
   private String child = "Child";
   // Getters/setter omitted

Jackson also respects inheritance and places the parent before the child, but Gson does not respect this ordering. Table 6 shows how all three frameworks handle the serialization of a Child class.

Table 6. Summary of each framework’s default handling of field visibility
JSON BindingJacksonGson
  "parentName": "Parent",
  "child": "Child"
  "parentName": "Parent",
  "child": "Child"
  "child": "Child",
  "parentName": "Parent"

Support for JSON Processing types

Currently, only the JSON Binding API provides support for JSON Processing types (JSR 374), though Jackson does provide an extension module that can handle JSON Processing types. Recall from Part 1 that JSON Processing types model JSON structures. For example, the JsonObject class models the JSON object and JsonArray models JSON arrays. The JSON Binding API also provides two models for generating JSON structures. Both Jackson and Gson attempt to serialize JSON Processing instances directly from the underlying Map implementation, as shown in Table 7.

Table 7. Serialization of JSONObject
JSON BindingJacksonGson
"jsonObject": {
  "firstName": "Alex",
  "lastName": "Theedom"
"jsonObject": {
  "firstName": {
    "chars": "Alex",
    "string": "Alex",
    "valueType": "STRING"
  "lastName": {
    "chars": "Theedom",
    "string": "Theedom",
    "valueType": "STRING"
"jsonObject": {
  "firstName": {
    "value": "Alex"
  "lastName": {
    "value": "Theedom"


Gson and Jackson are established and very popular JSON frameworks that offer an extensive range of features, especially for customization. As I have demonstrated, however, there is very little consistency in how simple scenarios are handled across the frameworks. Even the simplest matter of null treatment differs.

Where there is consistency in the handling of types, it is because there could be no other option. Basic types can only realistically be serialized as their actual value, while maps and collections translate directly to their JSON object equivalents, and vice versa. The most stark inconsistency is the treatment of dates and times: Not one of the frameworks serializes these types in the same way. Although there are configuration options to control formats, the range of inconsistency points to the need for a standard.

Developers shouldn’t be forced to compromise performance for features, and a JSON binding standard would resolve that. I believe adopting a JSON binding standard is the next evolutionary step for JSON, and that JSR 367 is the right specification to do it. What do you think? I hope you’ll Please share your thoughts below.

Downloadable resources

Related topics


Sign in or register to add and subscribe to comments.

Zone=Java development
ArticleTitle=Get started with the JSON Binding API, Part 4: Is it time for a JSON binding standard?