Deconstructors

December 6, 2025 · View on GitHub

◀︎ RecordBuilder • Deconstructors

Deconstructors

Deconstructors have been proposed for a future JDK. RecordBuilder's deconstructor support is based on this proposal. See: https://github.com/openjdk/amber-docs/blob/master/eg-drafts/deconstruction-patterns-records-and-classes.md#deconstructors-for-classes. Quoting the proposal:

A deconstruction pattern can be thought of as the dual of a constructor; a constructor takes N state components and aggregates them into an object, and a deconstruction pattern takes an object and decomposes it into N state components.

JDK records have implicit deconstructors that are used throughout the JDK for pattern matching, etc. However, standard Java classes do not have deconstructors. RecordBuilder can generate records and builders from deconstructor-style methods in any Java class.

A deconstructor is either:

  1. a method in any class or interface that:
    • returns void
    • is not static
    • has one or more parameters of type:
      • Consumer<T>
      • IntConsumer
      • LongConsumer
      • DoubleConsumer
    • is annotated with @RecordBuilder.Deconstructor
  2. a class or interface that is:
    • annotated with @RecordBuilder.Deconstructor
    • has one or more accessor methods or fields annotated with @RecordBuilder.DeconstructorAccessor. If it is a field that is annotated, there must be a matching public, non-static method that is named either the same as the field or is prefixed with get or is.

When RecordBuilder encounters a deconstructor it generates a record that matches the deconstructor's parameters and, optionally, creates a record builder for it. The record's components are the same as the implied types of the deconstructor method's parameters or the return types of the deconstructor accessors. The record will have a static method named from (you can change this) that takes an instance of the class containing the deconstructor and returns an instance of the generated record. In other words, it deconstructs the class instance into a data access object that can be used in pattern matching, etc.

Example

Deconstructor method

Given this POJO:

public class MyClass {
    private final int qty;
    private final String name;
    
    public MyClass(int qty, String name) {
        this.qty = qty;
        this.name = name;
    }

    @Deconstructor
    public void deconstructor(IntConsumer qty, Consumer<String> name) {
        qty.accept(this.qty);
        name.accept(this.name);
    }
}

RecordBuilder will generate a record (and record builder) similar to:

public record MyClassDao(int qty, String name) {
    public static MyClassDao from(MyClass rhs) {
        MyClassDaoBuilder builder = MyClassDaoBuilder.builder();
        rhs.deconstructor(builder::qty, builder::name);
        return builder.build();
    }
}

Deconstructor accessor

Given this POJO:

@Deconstructor
public class MyClass {
    private final int qty;
    private final String name;
    
    private MyClass(int qty, String name) {
        this.qty = qty;
        this.name = name;
    }

    @DeconstructorAccessor
    public int getQty() {
        return qty;
    }

    @DeconstructorAccessor
    public String getName() {
        return name;
    }
    
    // etc.
}

RecordBuilder will generate a record (and record builder) similar to:

public record MyClassDao(int qty, String name) {
    public static MyClassDao from(MyClass rhs) {
        MyClassDaoBuilder builder = MyClassDaoBuilder.builder();
        builder.qty(rhs.getQty()).name(rhs.getName());
        return builder.build();
    }
}

Usage

You can use it in switch statements, etc.:

MyClass myClass = ...
        
switch (MyClassDao.from(myClass)) {
    case MyClassDao(var qty, var name) when qty > 0 -> ...
    case MyClassDao(var qty, var name) when qty == 0 -> ...
    case MyClassDao(var qty, var name) when name.isEmpty() -> ...
    ... etc. ...
}

Options

The @RecordBuilder.Deconstructor annotation has several options that control how the record is generated. See the definition for details.

When addRecordBuilder() is true a RecordBuilder is created for the generated record. You can add a @RecordBuilder.Options annotation to the deconstructor method to control how the record builder is generated. You can also create a @DeconstructorTemplate to build a custom deconstructor annotation. It is created in the same manner as a custom @Template annotation. See Customizing for details.

A pre-built template, @DeconstructorFull can be used which is an analog to @RecordBuilderFull.