Java record withers what and when

This blog post is the result of searching the internet to find out what Java records with “withers” will look like and in what version of Java records with withers will arrive.

In addition, while trying to figure out Java withers, I discovered that reforming my current beans with builders into records with builders, I could throw away half the boiler plate code.

Intro

In Build beans better with builders I concluded that I would continue with my practice of hand-writing read-only beans with builders, because java records don’t have a copy with mutate behaviour.

I.e. that when you need to change one or more values in a data structure, you do so by copying the original and replacing the desired values in the copy.

The results are the original object (that you typically drop) and a new object with the desired values.

This is a pattern that I use a lot in JavaScript:

const a = { b: 1234, pi: 3.14, e: 2.72, hi: 'Hello world!' };
console.log(a);

// Here's the copy-and-mutate
// We copy all of a with "...a"
// then replace the field "hi"
const newA = { ...a, hi: 'I\'m replaced!' };
console.log(newA);

With my beans-with-builders I get the same thing, and the syntax for creating beans and doing copy-with-mutate is kinda nice:

var a = A.with().b(1234).pi(3.14).e(2.72).hi("Hello world!").build();
System.out.println(a);

var newA = A.with(a).hi("I'm replaced!").build();
System.out.println(newA);

What’s not shown in this example is the verbose boilerplate code for manually defining bean A and its builder (it’s here, in case anyone cares). The getters and the toString() method are all autogenerated by my IDE, but it is a lot of boilerplate that will sleep untouched by human hands from the time it is first created, just to prop up my nice syntax.

Alternatively I could have used lombok which would have let me define the bean like so:

@Getter
@Builder(toBuilder = true)
public class A {
    int a;
    double pi;
    double e;
    String hi;
}

and then use like this:

var a = A.builder().b(1234).pi(3.14).e(2.72).hi("Hello world!").build();
System.out.println(a);

var newA = a.toBuilder().hi("I'm replaced!").build();
System.out.println(newA);

So, much less boilerplate, and a builder syntax I could live with. But this comes at the cost of an added build time dependency that is likely to bite me every time I upgrade java version, IDE or build tools. Or it may even be broken by a transitive dependency of a completely unrelated upgrade.

So, what I ended up doing, is bite the bullet and create my boilerplate code, hide it away, and try not to think about it anymore until I have to change it for some reason.

I’m aware that Kotlin and Scala already has support for this behaviour and are both running on the JVM.

But I want a mainstream Java solution.

At JavaZone 2023 I asked if there are plans to add copy and mutate functionality to records and was told that there is.

This blog post is the results of me trying to find what this behaviour will look like and when it will arrive.

Using the current records with builders

I am currently on Java 17, which has records.

So the first question to ask, is: can you use builders with records?

And it turns out you can use builders: here is the A bean of the previous example in the form of a record with a builder.

Less clutter than with the original example: no need for generated getters or toString() or for the hashCode() provided by deriving from Immutable.

And the usage of record with builder is identical to the bean with builder example.

So this is something to consider as an intermediate step.

But the builder added to the record is still pure boilerplate I would like get rid of.

Note that it is possible to use lombok with records, but not much to gain, syntactically, compared to lombok with classes. And with the same build/upgrade complexities added to your project as when using lombok with classes.

There is also a newcomer called RecordBuilder.

RecordBuilder is similar to lombok: it uses annotations on the records to autogenerate the builder stuff at compile time. I haven’t studied it as closely as lombok, but I suspect it will have similar downsides.

Native Java withers

Two things I would like to figure out:

  1. What will it look like?
  2. When will it arrive? (can it be tracked to an actual Java version number?)

So, what will it look like? I have tried trawling the internet for answers.

Brian Goetz seems central in the work with withers, and is also seems possibly the one who have coined the term “withers”?

In July 2020 Brian Goetz wrote a post on mailing list amber-spec-experts in response to what seems to be a response to a proposal that I can’t find. And both Brian Goetz and the person he’s quoting doesn’t seem to like the proposal of using with. There is a mention of C# 9 syntax for “with”, where this blog post’s initial example would be a data class defined like:

public data class A { int B; double Pi; double e; string Hi; }

and used like this:

var a = new A { B = 1234; Pi = 3.14; E = 2.72; Hi = "Hello world!"; };
Trace.WriteLine(a);

var newA = a with { Hi = "I'm replaced!"; };
Trace.WriteLine(newA);

Brian Goetz describes the C# syntax as “one way to get there”, but that there are roadblocks that needs to be cleared, without going into specifics.

On August 12 2021, Brian Goetz wrote Functional Transformation of Immutable Objects which explores several ways of doing it. The draft uses examples from C#, which has more than one way of doing the task (the new with construct as well as named constructor parameters with default values) and proposed using the with syntax of C# and showed how it could be implemented based the existing Java building blocks constructors and deconstructors.

A Java version of the C# example above, would require a record:

public record A(int b, double pi, double e, String hi) { }

and usage of this record type would be very similar to the C# data class above:

var a = new A(1234, 3.14, 2.72, "Hello world!");
System.out.println(a);

var newA = a with { hi = "I'm replaced!" };
System.out.println(newA);

On June 10 2022 (10 months later) Brian Goetz posted a message about the draft “Functional Transformation of Immutable objects to the amber-spec-experts mailing list and also outlines a way to cheaply utilize the with mechanism of the draft to create a “builder”, by creating a static instance of the record:

public record A(int b, double pi, double e, String hi) {
    private A() {
        this(0, 0, 0, null);
    }

    public static A BUILDER = new A();
}

and then use the “builder” like so:

var a = A.BUILDER with { b=1234; pi=3.14; e=2.72; hi="Hello world!"; };
System.out.println(a);

var newA = a with { hi = "I'm replaced!" };
System.out.println(newA);

It looks pretty much like what I want and I would be happy to lose the final builder boilerplate from my records.

The JEP (JDK Enhancement Proposal) for “withers” is JEP 468: Derived Record Creation (Preview) and as far as I can tell it doesn’t differ much from the draft (my examples would still look the same).

So withers looks good.

But when can we expect withers in Java?

Brian Goetz’s post to amber-spec-experts started a thread with subject “With” for records – Brian Goetz on reddit on June 10 2022 but I couldn’t find any roadmap there.

Project Amber, that Brian Goetz’ draft and the mailing list he posted to belongs to, has a charter that says that it is “to explore and incubate smaller, productivity-oriented Java language features that have been accepted as candidate JEPs in the OpenJDK JEP Process”. So things in Project Amber aren’t as such tied to any particular Java version release.

The JEP for “withers”, JEP 468: Derived Record Creation (Preview), mentions Java 23, which is targeted for June 2024.

Also, there is an Inside Java Newscast that mentions Java 23 as the target.

Unfortunately JEP 468 is not on Java 23’s own list of JEPs at least not yet.

But we can hope: if not in Java 23, then maybe in the next one?

But in the meantime I will start out by transforming my current immutable beans with builders to records with builders. This will cut the boiler plate of Build beans better with builders in half, with nearly the same syntax as today.

One thought on “Java record withers what and when”

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.