Java 14 brings a new experimental feature: records. Records are a new "class" type which could be presented as "value" classes. For scala developpers, it is close to case classes or for java developpers it is the @Data of lombok but without the POJO convention. Let see how it integrates with JSON-B.

A record is as simple as a class with an implicit constructor:

record Foo(int property1, String property2) {}

You can also decorate the properties:

record Foo(int property1, @JsonbProperty("bar") String property2) {}

The first step to compile this sample is to add the flag --enable-preview to javac (and we will need to do the same to the java command). For maven it can look like:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.8.1</version>
  <configuration>
    <release>14</release>
    <forceJavacCompilerUse>true</forceJavacCompilerUse>
    <compilerArgs>
      <compilerArg>--enable-preview</compilerArg>
    </compilerArgs>
  </configuration>
</plugin>

If you compile and decompile the record type, you will likely get this:

final class com.github.rmannibucau.record.Foo extends java.lang.Record { // 1
  public com.github.rmannibucau.record.Foo(int, java.lang.String); // 2

  // 3
  public int property1();
  public java.lang.String property2();

  // 4
  public java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
}
  1. Record base class just enforces a contract but is not super useful by itself in the context of this post (but it is for libraries, see 4.)
  2. A cnstructor is generated for the properties of the class
  3. An accessor is generated per property but it does not respect POJO convention, it uses the same name than the property - note that properties 
  4. Record contract is implemented enforcing toString()hashCode() and equals() to be implemented automatically (this is what can be useful for libraries, ensuring these methods are there open several doors).
  5. Something not that visible in the snippet is that parameters of the constructor are named - first parameter is named property1 and not arg0 as usual - which will be key for us.

The issue is obvious: default JSON-B mapping does not work since there is no getter and there is no default constructor so serialization AND deserialization will not work.

For the deserialization you can add @JsonbCreator on the constructor like that:

record Foo(int property1, String property2) {
  @JsonbCreator
  public Foo { // the implicit constructor is called anyway to enforce this "value" behavior
  }
}

At this stage, it enables to deserialize the record - even if it broke the simplicity of record classes.

For the serialization, you can have the same kind of trick: add getters....but then you get a POJO implementing record constract (which is a bit better but will not help with the verbosity).

The last trick you will hit is the renaming of properties. We saw we can add @JsonbProperty on the properties, this will work for the fields but not the constructor so deserialization will not respect that.

So are we condamned to use POJO? Yes and no :). Today, if you want to be portable, you will need to. However, next release of Apache Johnzon supports out of the box record types so you can just write:

record Foo(int property1, @JsonbProperty("bar") String property2) {}

With that, if you run this code:

final var foo = new Foo(1, "foo");
try (final var jsonb = JsonbBuilder.create()) {
  System.out.println("json=" + jsonb.toJson(foo)); // 1
  System.out.println("object=" + jsonb.fromJson("{\"bar\":\"bar\",\"property1\":3}", Foo.class)); // 2
}

It will output:

json={"bar":"foo","property1":1} // 1
object=Foo[property1=3, property2=bar] // 2
  1. The serialization mapped both properties of the record and used the inline renaming of property2 as bar,
  2. The deserialization filled both properties, respecting the renaming of properties2 which was named bar in the JSON

So this shows that record classes will likely need some JSON-B specification itnegration but that there is no technical blocker to make it work.

This is a big promise for future java versions since it will enable to write more fluently anemic structures (API DTO for instance) but also to use pattern/expression matching more easily in our code.

From the same author:

In the same category: