java withers - derived record creationjava withers - derived record creation
HappyCoders Glasses

Withers in Java: “Derived Record Creation Expressions”

Sven Woltmann
Sven Woltmann
Last update: September 4, 2025

Derived Record Creation Expressions (or, in short: Withers) are a concise way to create records derived from Java records that differ from the original record in one or more (or even no) fields.

In this article you will find out:

  • Why do we need Derived Record Creation Expressions (Withers)?
  • What are explicit wither methods?
  • How do Derived Record Creation Expressions work?
  • What restrictions apply when using Derived Record Creation Expressions?

Derived Record Creation Expressions will be released as a preview feature in one of the upcoming Java versions. Which Java version that will be is currently not clear, as the corresponding JDK Enhancement Proposal 468 is still in Candidate status.

Why Do We Need Withers?

Java Records are immutable – and that’s a good thing. Because immutability makes code more understandable, reliable, and secure. But there are always use cases in which we want to derive a new record from an existing record that differs from the existing record in only one or a few fields.

I would like to show you this with an example – namely with the following record:

public record Point3D(double x, double y, double z) { }Code language: Java (java)

Let’s assume we have an existing Point3D point and now only want to increase the Z coordinate by 10.0. Then we have to create a new record as follows:

Point3D pointNew = new Point3D(point.x(), point.y(), point.z() + 10.0);Code language: Java (java)

So we have to read all fields from the existing record and then specify all fields for the new record – the changed ones and also the unchanged ones. On the one hand, this is complex, and on the other hand, it can quickly become error-prone – especially with more complex records.

Wouldn’t it be nice if we only had to specify the fields that have changed?

Previous Solution: Explicit Wither Methods

One way to make the work easier for the “users” of the record is to provide so-called wither methods. These are methods within the record that return a derived record with one (or more) changed fields.

In our Point3D, for example, we could provide the following wither methods:

public record Point3D(double x, double y, double z) {
  public Point3D withX(double newX) {
    return new Point3D(newX, y, z);
  }

  public Point3D withY(double newY) {
    return new Point3D(x, newY, z);
  }

  public Point3D withZ(double newZ) {
    return new Point3D(x, y, newZ);
  }
}Code language: Java (java)

This now allows us to change the Z coordinate as follows:

Point3D pointNew = point.withZ(point.z() + 10.0);Code language: Java (java)

Disadvantages of Explicit Wither Methods

Explicit wither methods have two disadvantages.

The first is obvious:

We have to implement a lot of boilerplate code, and this represents an increased implementation and maintenance effort.

The second disadvantage:

If there are semantic restrictions on the combination of several fields of a record, these fields may not be changed individually. Let’s assume that the distance of our Point3D from the origin of the coordinate system must not be greater than 100.0.

We could ensure this relatively easily with the following compact constructor:

public record Point3D(double x, double y, double z) {
  public Point3D {
    double distance = Math.sqrt(x * x + y * y + z * z);
    if (distance > 100.0) {
      throw new IllegalArgumentException("Point lies outside the allowed distance " +
                                         "of 100 units from origin (0, 0, 0).");
    }

  // . . .
}Code language: Java (java)

Let’s assume we have a point with the coordinates (0, 80, 10) and want to swap its X and Y coordinates:

Point3D point = new Point3D(0, 80, 10);
Point3D pointNew = point
    .withX(point.y())
    .withY(point.x());Code language: Java (java)

Unfortunately, this leads to an IllegalArgumentException, because in the first step – i.e. when calling withX(point.y()) – an attempt is made to create a point with the coordinates (80, 80, 10).

So we would have to provide another wither method in Point3D that sets both X and Y coordinates (a so-called Compound Wither Method):

public record Point3D(double x, double y, double z) {
  // . . .

  public Point3D withXY(double newX, double newY) {
    return new Point3D(newX, newY, z);
  }

  // . . .
}Code language: Java (java)

The following call would then be successful:

Point3D point = new Point3D(0, 80, 10);
Point3D pointNew = point.withXY(point.y(), point.x());Code language: Java (java)

If we want to swap other coordinate pairs, we would need correspondingly more wither methods. With more complex records, this quickly becomes confusing and error-prone.

The Solution: Derived Record Creation Expressions

Wouldn’t it be nicer if we could avoid all the boilerplate code and concentrate on specifying which record components should change in what way?

JEP 468 will make exactly that possible with Derived Record Creation Expressions!

Note:
Derived Record Creation Expressions are currently not available in any Java version, i.e. you have to be patient until you can try out the following code.

First of all, we remove all explicit wither methods from our record – it now looks like it did at the very beginning:

public record Point3D(double x, double y, double z) { }Code language: Java (java)

To increase the Z coordinate by 10.0, for example, we can simply use the new with keyword:

Point3D pointNew = point with {
  z += 10;
};Code language: Java (java)

This means: “Create a new record with z increased by 10.0”. This code is much more readable and maintainable than anything before, as it only focuses on what changes and does not require any further boilerplate code.

A few more examples...

x and y could be swapped as follows:

Point3D pointNew = point with {
  double helper = x;
  x = y;
  y = helper;
};Code language: Java (java)

We could multiply all coordinates by 2.0 like this:

Point3D pointNew = point with {
  x *= 2.0;
  y *= 2.0;
  z *= 2.0;
};Code language: Java (java)

We can also call the with keyword multiple times – multiplying all coordinates by 2.0 would also be possible as follows:

Point3D pointNew = point 
    with { x *= 2.0 }
    with { y *= 2.0 }
    with { z *= 2.0 };Code language: Java (java)

But be careful: The last two examples are not identical! In the first, one new record is created. In the second, three new records are created – one for each with call. This is more effort on the one hand, and on the other hand, validations that relate to the combination of several fields could fail.

More on this in the next section.

How Exactly Does Derived Record Creation Work?

I will explain the exact functionality of Derived Record Creation Expressions using the first with example from above – here it is again:

Point3D pointNew = point with {
  z += 10;
};Code language: Java (java)

A Derived Record Creation Expression consists of three parts:

  1. the Origin Expression – in the example: point
  2. the keyword with
  3. the Transformation Block – in the example: { z += 10; }

Within the transformation block, all fields of the original record are provided in local, changeable variables by calling its accessor methods. It is as if the following code were executed before the transformation block is executed:

double x = x();
double y = y();
double z = z();Code language: Java (java)

So the fields are not accessed directly: If the accessor methods have been overwritten and contain further logic, this is executed.

Then the transformation block is executed – in the example, the local variable z is increased by 10.0.

At the end of the transformation block, a new record is created based on the (possibly changed) local variables, i.e. like this:

new Point3D(x, y, z)Code language: Java (java)

The record constructor is also actually called to execute any validations that may be present there.

This also helps you understand why the last two examples in the previous section are different: When with is called once, the constructor is called once – with all changes. When with is called several times, the constructor is called mehrfach – each time with only one change. In this way, validations that relate to the combination of several parameters would fail in the event of an invalid intermediate state.

Here are a few more notes:

  • If the original expression – in the example point – is null, an NullPointerException occurs.
  • The transformation block may also be empty – in this case, an unchanged copy of the original record is returned.
  • If a variable exists outside the Derived Record Creation Expression with the same name as a field of the record, then it is not visible within the transformation block (it is “shadowed”).

Here is the third point explained again with an example:

double x = 50;

Point3D point = new Point3D(10, 20, 30);
Point3D pointNew = point with {
  // x is 10 here, not 50.
  // The "outer" x is not visible here.
  x = 20;
}

// x is 50 here, not 20.
// The "inner" x is not visible here.Code language: Java (java)

Nested Derived Record Creation

In the case of nested records, the with expressions can also be nested. The following record defines a line in three-dimensional space:

public record Line3D(Point3D start, Point3D end) { }Code language: Java (java)

Let’s create such a line:

Line3D line = new Line3D(new Point3D(1, 2, 3), new Point3D(4, 5, 6));Code language: Java (java)

Then we could now change the end point as follows (not yet nested):

line = line with { 
  end = new Point3D(4, 5, 10); 
};Code language: Java (java)

The new end point differs from the original end point only in the Z coordinate. We can also write this more concisely as follows – with nested with expressions:

line = line with { 
  end = end with { z = 10; }
};Code language: Java (java)

Inner Derived Record Creation

Derived Record Creation Expressions may also be used within the record. For example, we could offer a scaling method for the Point3D record:

public record Point3D(double x, double y, double z) {
  // . . .

  public Point3D scale(double factor) {
    return this with {
      x *= factor;
      y *= factor;
      z *= factor;
    };    
  }
}Code language: Java (java)

Restrictions of Derived Record Creation Expressions

The following restrictions apply to Derived Record Creation Expressions:

  • The transformation block must not contain an return expression.
  • The transformation block must not contain an yield, break or continue expression whose target lies outside the transformation block.

I will also explain the second point again with an example.

The following (admittedly strongly constructed) code, in which the targets of yield and break lie within the transformation block, is permitted, for example:

Point3D point = new Point3D(10, 20, 30);
Point3D pointNew = point with {
  y = switch (x) {
    case double d when d < 0.0 -> -1;
    case double d when d > 0.0 -> {
      double newValue = d;
      for (int i = 0; i < 10; i++) {
        newValue *= 2.0;
        if (newValue > 100.0) break;  // allowed
      }
      yield newValue;  // allowed
    }
    default -> 0;
  };
}Code language: Java (java)

The following (also strongly constructed) code, on the other hand, would not be allowed, since here the target of break would be the for loop outside the Derived Record Creation Expression:

Point3D point = new Point3D(10, 20, 30);
for (int i = 0; i < 10; i++) {
  point = point with {
    if (x > 0.0) {
      x -= 1.0;
    } else {
      break;  // not allowed
    }
  };
}Code language: Java (java)

Conclusion

Derived Record Creation Expressions are a concise syntax for creating derived records and specifying only the changed fields – without having to write explicit, maintenance-intensive wither methods yourself.

Unfortunately, it is currently not possible to predict when Derived Record Creation will be available – as soon as that changes, you will find out here early.