Advanced Record Utils is an annotation-processor based source code generator that creates a companion *Utils class (e.g., PersonUtils for a Person record) that contains:

  • A "Builder" for the record

  • A "With" interface

  • A "Merger" utility and interface

  • An "XML" utility and interface for serialisation to XML

  • A "Diff" interface and result class

  • An "All" interface that bundles all the other interfaces together

It works recursively through a structure to create a "tree" of records, can import all records/interfaces in a package, or import specific records/interfaces from libraries or previously compiled modules.

Feel free to jump to the quick start if you are in a hurry, or use the navigation options on the left to find a specific documentation item.

1. Reading this document

The following are some details about how this document is written and intended to be read.

1.1. Callouts/Admonitions

Throughout this documentation, you will find several types of admonition blocks used to highlight information. Understanding their meaning will help you get the most out of this guide.

This is a tip. A tip can offer a helpful suggestion, a shortcut, or a best practice that can improve your experience or efficiency when using the library.
This is a note. A note contains supplementary information that might be relevant but isn’t critical to the main point. Think of it as an "FYI" or a "by the way."
This is something important. This highlights information that is crucial for the correct functioning of a feature.
This is something to be cautious of. These generally advise you against a course of action, to inform you of potential challenges, or to act carefully.
This is a warning. Ignoring a warning is very likely to result in significant problems. Warnings are used to call out things that will break your code, or segments of code that are deliberately bad examples.

1.2. Collapsible segments

Generated code - and sometimes other items - is contained in collapsible segments like the following:

Knock, knock

Who’s there?

1.3. Code blocks

Throughout this document, you’ll see code blocks that look like this:

Person.java
public record Person(String name, int age, List<String> favouriteColours) {}

Code blocks in this document do not include import statements.

2. Get started

We recommend the approach of adding the annotations as a dependency and the processor to your Maven Compiler Plugin configuration. This is so that dependencies that the processor has don’t end up in your project, and because the default in Java 23+ is to not automatically scan dependencies for annotation processors (which could be a security risk!)

2.1. Quick Start

Quick Start - Maven

The latest version of Advanced Record Utils is

Maven Central release

Merge the following with your existing Maven pom.xml:

pom.xml
<project>
    <properties>
        <aru.version>0.6.5</aru.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>io.github.cbarlin</groupId>
            <artifactId>advanced-record-utils-annotations</artifactId>
            <version>${aru.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>io.github.cbarlin</groupId>
                            <artifactId>advanced-record-utils-processor</artifactId>
                            <version>${aru.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Annotate a record like so:

Person.java
@AdvancedRecordUtils
public record Person (
    String name,
    int age,
    List<String> favouriteColours
) implements PersonUtils.All {}

Start using the generated builder/wither:

Sample method body
Person personA = PersonUtils.builder()
  .name("Rufus")
  .age(30)
  .addFavouriteColours("blue")
  .addFavouriteColours("purple")
  .build();
Quick Start - Maven (plus Reproducible Builds and Enforcer)
This is an opinionated setup for reproducible builds and using the enforcer to help maintain that. For a better understanding of reproducible builds, please check out the Reproducible Builds website and the Maven Documentation. For a better understanding of the enforcer for maven, please check out the Enforcer Plugin documentation.

The intent of this opinionated setup is to:

  • Ensure that outside of running a recent version of Maven, the version of Maven you are using is largely irrelevant

  • Ensure that versions of dependencies are exact versions, and therefore won’t suddenly change if your dependency changes (most Java projects already do this)

Merge the following with your existing Maven pom.xml:

pom.xml
<project>
    <properties>
        <!-- Timestamp for output - can be any time really -->
        <project.build.outputTimestamp>2025-09-08T10:01:11Z</project.build.outputTimestamp>
        <!-- Dependency versions -->
        <aru.version>0.6.5</aru.version>
        <!-- Plugin versions -->
        <maven-artifact-plugin.version>3.6.0</maven-artifact-plugin.version>
        <maven-clean-plugin.version>3.5.0</maven-clean-plugin.version>
        <maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
        <maven-dependency-plugin.version>3.8.1</maven-dependency-plugin.version>
        <maven-deploy-plugin.version>3.1.4</maven-deploy-plugin.version>
        <maven-enforcer-plugin.version>3.6.1</maven-enforcer-plugin.version>
        <maven-install-plugin.version>3.1.4</maven-install-plugin.version>
        <maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
        <maven-javadoc-plugin.version>3.11.3</maven-javadoc-plugin.version>
        <maven-project-info-reports-plugin.version>3.9.0</maven-project-info-reports-plugin.version>
        <maven-release-plugin.version>3.1.1</maven-release-plugin.version>
        <maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
        <maven-site-plugin.version>3.21.0</maven-site-plugin.version>
        <maven-source-plugin.version>3.3.1</maven-source-plugin.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>io.github.cbarlin</groupId>
            <artifactId>advanced-record-utils-annotations</artifactId>
            <version>${aru.version}</version>
        </dependency>
    </dependencies>
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-artifact-plugin</artifactId>
                    <version>${maven-artifact-plugin.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-dependency-plugin</artifactId>
                    <version>${maven-dependency-plugin.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>${maven-clean-plugin.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>${maven-resources-plugin.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>${maven-compiler-plugin.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>${maven-jar-plugin.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>${maven-install-plugin.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>${maven-deploy-plugin.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-site-plugin</artifactId>
                    <version>${maven-site-plugin.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-project-info-reports-plugin</artifactId>
                    <version>${maven-project-info-reports-plugin.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-enforcer-plugin</artifactId>
                    <version>${maven-enforcer-plugin.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-source-plugin</artifactId>
                    <version>${maven-source-plugin.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-javadoc-plugin</artifactId>
                    <version>${maven-javadoc-plugin.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-release-plugin</artifactId>
                    <version>${maven-release-plugin.version}</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>io.github.cbarlin</groupId>
                            <artifactId>advanced-record-utils-processor</artifactId>
                            <version>${aru.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-enforcer-plugin</artifactId>
                <executions>
                    <execution>
                        <id>ban-dynamic-versions</id>
                        <goals>
                            <goal>enforce</goal>
                        </goals>
                        <configuration>
                            <rules>
                                <banDynamicVersions />
                                <dependencyConvergence />
                                <reactorModuleConvergence />
                                <banDuplicatePomDependencyVersions />
                                <requirePluginVersions />
                            </rules>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

You can then run the following to ensure your build is reproducible:

Command
mvn clean install && mvn clean verify artifact:compare

2.2. Configuring your build tooling

2.2.1. Maven

  1. Add a property for the version of Advanced Record Utils. The current version is

    Maven Central release

    This guide will assume that the property is named aru.version.

  2. Add the annotations as a dependency:

    pom.xml
    <dependency>
        <groupId>io.github.cbarlin</groupId>
        <artifactId>advanced-record-utils-annotations</artifactId>
        <version>${aru.version}</version>
    </dependency>
  3. Add the annotation processor to your annotation processor paths:

    If you are using some optional integrations with e.g. avaje-jsonb, then ensure that our processor comes first
    pom.xml
    <path>
        <groupId>io.github.cbarlin</groupId>
        <artifactId>advanced-record-utils-processor</artifactId>
        <version>${aru.version}</version>
    </path>
    Where does this block go?

    If you don’t have a build section of your pom, you can use the below.

    pom.xml
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>io.github.cbarlin</groupId>
                            <artifactId>advanced-record-utils-processor</artifactId>
                            <version>${aru.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>

2.2.2. Gradle

Gradle 5.2 added better support for annotation processors, so ensure you are using that version or newer.

  1. Add a property for the version of Advanced Record Utils. The current version is

    Maven Central release

    This guide will assume that the property is named aru.version.

  2. Add the declarations for the dependency and the annotation processor like so:

    Gradle file
    dependencies {
      implementation("io.github.cbarlin:advanced-record-utils-annotations:${aru.version}")
      annotationProcessor("io.github.cbarlin:advanced-record-utils-processor:${aru.version}")
      testAnnotationProcessor("io.github.cbarlin:advanced-record-utils-processor:${aru.version}")
    }

2.3. Java modules

If you are using Java’s module system, you will need to add the below to your module-info.java file:

module-info.java
module org.example {
    requires io.github.cbarlin.aru.annotations;
    // No explicit 'requires org.jspecify' needed; it's re-exported transitively by the annotations module
}

2.4. Optional dependencies

This library has optional integration with several dependencies. Some of these integrations, such as XML serialisations, are enabled based on the options you chose and are mandatory for those options. Where an option would require you to have a dependency, this will be documented.

Other integrations are based purely on detecting the dependency in your classpath and using them where it makes sense to do so without you being required to do anything. These integrations are:

  • Eclipse Collections - Collections from Eclipse Collections are detected if referenced and handled appropriately (e.g. creating ImmutableList if requested).

  • Apache Commons Lang 3 - Commons Lang 3, if detected, will be used for Validate and StringUtils classes

  • MapStruct - Integration has been made on our end so that MapStruct will use our builders.

2.5. Reproducible Builds

Reproducible Builds

This project is reproducible (and independently verified - thanks hboutemy!). In addition, our GitHub builds ensure that the generated code is also reproducible.

Assuming you have done everything else you need to do for reproducible builds (see the JVM guide) then use of Advanced Record Utils will not stop that (barring one edge case - see "Importing records/interfaces" below).

If your build isn’t reproducible and it should be, please create a new issue on our GitHub.
If you use MapStruct and want reproducible builds, you’ll need to set mapstruct.suppressGeneratorTimestamp=true and possibly also mapstruct.suppressGeneratorVersionInfoComment=true. See their documentation for details on how to set those settings.

2.6. Generated code

Code is usually generated in the target/generated-sources/annotations/. We aim to generate readable code.

The top-level *Utils class contains the builder static method to create a new builder. Depending on your settings, other static methods may be available. The *Utils class also contains other classes and interfaces, depending on your options.

Classes that begin with an _ (an underscore) are intended to be "internal". While these are still readable, you aren’t intended to directly use them. They are still public though, as other *Utils classes may use them. Static methods that you may wish to use should have a setting to expose them on the top-level *Utils class

The top-level *Utils class also has an @AdvancedRecordUtilsGenerated annotation on it. This contains metadata about the generated code and serves two purposes:

  • It’s used by the processor if the generated code is used in a library to re-create the "tree" of records (if applicable), as well as containing details about the settings used, generated classes, and version of the processor.

  • If there’s an issue involving generated code, it can be used to confirm things like the version and settings that were "detected" (see cascading and using a package to apply settings for details).

Generated methods and classes should have:

  • relevant Javadoc

  • a @Generated annotation that contains information that’s useful for debugging and "hiding" from code coverage tools.

We consider it a bug if a method, interface, or class doesn’t have Javadoc or if it is missing its a @Generated annotation.

3. Detailed Usage

3.1. Record usage

3.1.1. Basic usage

Annotate a record like so:

Person.java
@AdvancedRecordUtils
public record Person(String name, int age, List<String> favouriteColours) {}

When you build the class, the processor will take that and generate readable source code for you, which will then be compiled.

Full generated source code for default settings
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.Consumer;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;

/**
 * An auto-generated utility class to work with {@link Person} objects
 * <p>
 * This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
 * <p>
 * For more details, see the GitHub page for cbarlin/advanced-record-utils
 */
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
        generatedFor = Person.class,
        version = @AdvancedRecordUtilsGenerated.Version(
                major = 0,
                minor = 6,
                patch = 0
        ),
        settings = @AdvancedRecordUtils,
        internalUtils = {
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "All", implementation = PersonUtils.All.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "Builder", implementation = PersonUtils.Builder.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "With", implementation = PersonUtils.With.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "_MatchingInterface", implementation = PersonUtils._MatchingInterface.class)
        },
        references = {

        },
        usedTypeConverters = {

        }
)
public final class PersonUtils implements GeneratedUtil {
    /**
     * Create a blank builder of {@link Person}
     */
    @NonNull
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
            comments = "Related class claim: builderEmpty"
    )
    public static final Builder builder() {
        return Builder.builder();
    }

    /**
     * Creates a new builder of {@link Person} by copying an existing instance
     *
     * @param original The existing instance to copy
     */
    @NonNull
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
            comments = "Related class claim: builderCopy"
    )
    public static final Builder builder(final Person original) {
        return Builder.builder(original);
    }

    /**
     * A class used for building {@link Person} objects
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.BuilderClassCreatorVisitor"},
            comments = "Related class claim: builder"
    )
    public static final class Builder {
        @Nullable
        private int age;

        @NonNull
        private ArrayList<String> favouriteColours = new ArrayList<String>();

        @Nullable
        private String name;

        /**
         * Create a blank builder of {@link Person}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
                comments = "Related class claim: builderEmpty"
        )
        public static final Builder builder() {
            return new Builder();
        }

        /**
         * Creates a new builder of {@link Person} by copying an existing instance
         *
         * @param original The existing instance to copy
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
                comments = "Related class claim: builderCopy"
        )
        public static final Builder builder(final Person original) {
            Objects.requireNonNull(original, "Cannot copy a null instance");
            // "Copying an existing instance"
            return Builder.builder()
                    .name(original.name())
                    .age(original.age())
                    .favouriteColours(original.favouriteColours());
        }

        /**
         * Add a singular {@link String} to the collection for the field {@code favouriteColours}
         * <p>
         * Supplying a null value will set the current value to null
         *
         * @param favouriteColours A singular instance to be added to the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAdder"},
                comments = "Related component claim: builderAdd"
        )
        public Builder addFavouriteColours(@Nullable final String favouriteColours) {
            this.favouriteColours.add(favouriteColours);
            return this;
        }

        /**
         * Adds all elements of the provided collection to {@code favouriteColours}
         *
         * @param favouriteColours A collection to be merged into the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
                comments = "Related component claim: builderAddAllIterable"
        )
        public Builder addFavouriteColours(@NonNull final Collection<String> favouriteColours) {
            if (Objects.nonNull(favouriteColours)) {
                this.favouriteColours.addAll(favouriteColours);
            }
            return this;
        }

        /**
         * Adds all elements of the provided iterable to {@code favouriteColours}
         *
         * @param favouriteColours An iterable to be merged into the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
                comments = "Related component claim: builderAddAllIterable"
        )
        public Builder addFavouriteColours(@NonNull final Iterable<String> favouriteColours) {
            if (Objects.nonNull(favouriteColours)) {
                for (final String __addable : favouriteColours) {
                    this.addFavouriteColours(__addable);
                }
            }
            return this;
        }

        /**
         * Adds all elements of the provided iterator to {@code favouriteColours}
         *
         * @param favouriteColours An iterator to be merged into the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
                comments = "Related component claim: builderAddAllIterable"
        )
        public Builder addFavouriteColours(@NonNull final Iterator<String> favouriteColours) {
            if (Objects.nonNull(favouriteColours)) {
                while(favouriteColours.hasNext()) {
                    this.addFavouriteColours(favouriteColours.next());
                }
            }
            return this;
        }

        /**
         * Adds all elements of the provided spliterator to {@code favouriteColours}
         *
         * @param favouriteColours A spliterator to be merged into the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
                comments = "Related component claim: builderAddAllIterable"
        )
        public Builder addFavouriteColours(@NonNull final Spliterator<String> favouriteColours) {
            if (Objects.nonNull(favouriteColours)) {
                favouriteColours.forEachRemaining(this::addFavouriteColours);
            }
            return this;
        }

        /**
         * Returns the current value of {@code age}
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
                comments = "Related component claim: builderGetter"
        )
        public int age() {
            return this.age;
        }

        /**
         * Updates the value of {@code age}
         * <p>
         * Supplying a null value will set the current value to null
         *
         * @param age The replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
                comments = "Related component claim: builderPlainSetter"
        )
        public Builder age(@Nullable final int age) {
            this.age = age;
            return this;
        }

        /**
         * Creates a new instance of {@link Person} from the fields set on this builder
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddPlainBuild"},
                comments = "Related class claim: builderBuild"
        )
        public Person build() {
            // "Creating new instance"
            return new Person(
                    this.name(),
                    	this.age(),
                    	this.favouriteColours()
                    );
        }

        /**
         * Returns the current value of {@code favouriteColours}
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddGetter"},
                comments = "Related component claim: builderGetter"
        )
        public List<String> favouriteColours() {
            final List<String> ___immutable = this.favouriteColours.stream()
                        .filter(Objects::nonNull)
                        .toList();
            return ___immutable;
        }

        /**
         * Updates the value of {@code favouriteColours}
         * <p>
         * Supplying a null value will set the current value to null/empty
         *
         * @param favouriteColours The replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddSetter"},
                comments = "Related component claim: builderPlainSetter"
        )
        public Builder favouriteColours(@Nullable final List<String> favouriteColours) {
            this.favouriteColours.clear();
            if (Objects.nonNull(favouriteColours)) {
                this.favouriteColours.addAll(favouriteColours);
            }
            return this;
        }

        /**
         * Returns the current value of {@code name}
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
                comments = "Related component claim: builderGetter"
        )
        public String name() {
            return this.name;
        }

        /**
         * Updates the value of {@code name}
         * <p>
         * Supplying a null value will set the current value to null
         *
         * @param name The replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
                comments = "Related component claim: builderPlainSetter"
        )
        public Builder name(@Nullable final String name) {
            this.name = name;
            return this;
        }
    }

    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.AllInterfaceGenerator"},
            comments = "Related class claim: allIface"
    )
    public interface All extends With {
    }

    /**
     * An interface that provides the ability to create new instances of a record with modifications
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WitherPrismInterfaceFactory"},
            comments = "Related class claim: wither"
    )
    interface With extends _MatchingInterface {
        /**
         * Creates a builder with the current fields
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BackToBuilder"},
                comments = "Related class claim: witherToBuilder"
        )
        default Builder with() {
            return Builder.builder()
                    .name(this.name())
                    .age(this.age())
                    .favouriteColours(this.favouriteColours());
        }

        /**
         * Allows creation of a copy of this instance with some tweaks via a builder
         *
         * @param subBuilder A function to modify a new copy of the object
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BuilderFluent"},
                comments = "Related class claim: witherFluentBuilder"
        )
        default Person with(@NonNull final Consumer<Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final Builder ___builder = this.with();
            subBuilder.accept(___builder);
            return ___builder.build();
        }

        /**
         * Return a new instance with a different {@code favouriteColours} field
         *
         * @param favouriteColours Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithAdd"},
                comments = "Related component claim: witherWithAdd"
        )
        default Person withAddFavouriteColours(final List<String> favouriteColours) {
            return this.with()
                    .addFavouriteColours(favouriteColours)
                    .build();
        }

        /**
         * Return a new instance with a different {@code age} field
         *
         * @param age Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
                comments = "Related component claim: witherWith"
        )
        default Person withAge(final int age) {
            return this.with()
                    .age(age)
                    .build();
        }

        /**
         * Return a new instance with a different {@code favouriteColours} field
         *
         * @param favouriteColours Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
                comments = "Related component claim: witherWith"
        )
        default Person withFavouriteColours(final List<String> favouriteColours) {
            return this.with()
                    .favouriteColours(favouriteColours)
                    .build();
        }

        /**
         * Return a new instance with a different {@code name} field
         *
         * @param name Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
                comments = "Related component claim: witherWith"
        )
        default Person withName(final String name) {
            return this.with()
                    .name(name)
                    .build();
        }
    }

    @NullUnmarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceFactory"},
            comments = "Related component claim: internalMatchingIface"
    )
    interface _MatchingInterface {
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
                comments = "Related component claim: internalMatchingIface"
        )
        int age();

        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
                comments = "Related component claim: internalMatchingIface"
        )
        List<String> favouriteColours();

        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
                comments = "Related component claim: internalMatchingIface"
        )
        String name();
    }
}

And you’ll have access to a builder that you can use!

Sample method body
Person personA = PersonUtils.builder()
  .name("Rufus")
  .age(30)
  .addFavouriteColours("blue")
  .addFavouriteColours("purple")
  .build();
Person personB = PersonUtils.builder()
  .name("Aerith")
  .favouriteColours(List.of("red"))
  .build();

You can also "copy" an existing instance:

Sample method body
Person personC = PersonUtils.builder(personA)
  .name("Barret")
  .build();

3.1.2. Target Constructor

In a record with a non-canonical constructor that is a subset of the canonical one, the processor will generate the "Builder", "With", and "Merger" against that constructor instead of the canonical one.

A “subset constructor” is a non-canonical constructor whose parameter list is a proper subset of the canonical constructor’s parameters. The parameters have the same name and type, but some are omitted.

Take the following example:

SomeRecord.java
@AdvancedRecordUtils
public record SomeRecord (
    String a,
    String b,
    String c,
    String ab
) {
    public SomeRecord (String a, String b, String c) {
        this(a, b, c, "Mist");
    }
}

You are then not able to do:

With this example, the below code block will not compile.
Broken method body
SomeRecordUtils.builder()
    .ab("Inaba"); // Compile error - the method does not exist!

If you have multiple subset constructors to choose from, you can annotate the one you want the processor to use:

SomeRecord.java
@AdvancedRecordUtils
public record SomeRecord (
    String a,
    String b,
    String c,
    String ab
) {
    @AdvancedRecordUtils.TargetConstructor
    public SomeRecord (String a, String b, String c) {
        this(a, b, c, "I Mist");
    }

    public SomeRecord (String a, String b) {
        this(a, b, "I tried to catch fog yesterday.", "I Mist");
    }
}
If you have multiple subset constructors and do not annotate one, the processor will use the canonical one. You must be explicit.

3.1.3. Wither

The With and All interfaces are generated by default. See further down for details on these settings.

To use with methods (methods that create a new copy of the record with a field changed), you can implement the *Utils.All interface that’s generated for you.

Person.java
@AdvancedRecordUtils
public record Person(String name, int age, List<String> favouriteColours) implements PersonUtils.All { }
Implementing the All interface is preferable to implementing the With interface directly because if you enable more utilities they’ll be immediately available. Using the All interface also generates sealed interfaces.

And now you can use with style methods!

Sample method body
// personA and personB defined earlier
Person aButDifferentAge = personA.withAge(42); // Obtains a copy that has the `age` set to 42

PersonUtils.Builder backToBuilder = personB.with(); // Returns a builder instance that is pre-populated with the current values

Person aViaBuilder = personA.with(builder -> builder.name("Tifa")); // Fluent use of the builder, allowing for multiple changes to be made easily

The with methods generated broadly align with the same methods generated on the Builder. For example, if the builder has add methods enabled on collections (like addFavouriteColours) then the wither will have an withAdd method as well (withAddFavouriteColours), which creates a copy with the element having been added to that field.

Sample method body
Person withMoreColour = personA.withAddFavouriteColours("Cerulean");

3.1.4. Merger

The merger allows you to "merge" two instances of a record together. The merge process involves two instances of a record (personA and personB for example) and creates a new instance where the value of each field is determined like so:

  1. If both of the two instances have a null value, then the field is null

  2. If one of the two instances has a null value, then take the non-null value

  3. If both are non-null, and the field itself can be merged (i.e. another record that has a *Utils class with a merger), then merge the values using that other merger

  4. If both are non-null, and the field is a collection, then union the collections

  5. Otherwise, keep the value in the "preferred" instance

“Preferred instance” means the left-hand operand of the merge — for this.merge(other), this wins when both values are non-null and non-mergeable.
You can use Optional (or OptionalInt, OptionalLong, or OptionalDouble) instead of nullable fields - the merger will treat an empty optional the same as a null value

To enable the generation of a Mergeable interface, enable the merger setting. To use, implement the All (or Mergeable) interface like so:

Person.java
@AdvancedRecordUtils(merger = true)
public record Person(String name, Integer age, List<String> favouriteColours) implements PersonUtils.All {}
Merging primitives will always take the "preferred" instance as there is no null, so this example has swapped to a boxed value. An OptionalInt would also work for this example.
The generated code does not account for the possibility of cyclic references.
Generated Utils subclasses
public final class PersonUtils implements GeneratedUtil {
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
            comments = "Related class claim: mergerStaticClass"
    )
    public static final class _MergerUtils {
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
                comments = "Related class claim: mergerStaticClass"
        )
        private _MergerUtils() {
            throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
        }

        /**
         * Merge two instances of {@link Person} together
         *
         * @param preferred The preferred element
         * @param other The non-preferred element
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.MergeMethod"},
                comments = "Related class claim: mergeStaticMergeMethod"
        )
        public static Person merge(@Nullable final Person preferred, @Nullable final Person other) {
            if (Objects.isNull(other))  {
                // "Short-circuit of merge - other is null"
                return preferred;
            } else if (Objects.isNull(preferred)) {
                // "Short-circuit of merge - preferred is null"
                return other;
            }
            // "Merging two instances together"
            return Builder.builder()
                    .name(_MergerUtils.mergeString(preferred.name(), other.name()))
                    .age(_MergerUtils.mergeInteger(preferred.age(), other.age()))
                    .favouriteColours(_MergerUtils.mergeListString(preferred.favouriteColours(), other.favouriteColours()))
                    .build();
        }

        /**
         * Merger for fields of class {@link Integer}
         *
         * @param elA The preferred input
         * @param elB The non-preferred input
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.Fallback"},
                comments = "Related component claim: mergerAddFieldMergerMethod"
        )
        private static final Integer mergeInteger(@Nullable final Integer elA, @Nullable final Integer elB) {
            if (Objects.isNull(elA)) {
                return elB;
            }
            return elA;
        }

        /**
         * Merger for fields of class {@link List<String>}
         *
         * @param elA The preferred input
         * @param elB The non-preferred input
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.CollectionMerge"},
                comments = "Related component claim: mergerAddFieldMergerMethod"
        )
        private static final List<String> mergeListString(@Nullable final List<String> elA, @Nullable final List<String> elB) {
            if (Objects.isNull(elA) || elA.isEmpty()) {
                return elB;
            } else if (Objects.isNull(elB) || elB.isEmpty()) {
                return elA;
            }
            final ArrayList<String> combined = new ArrayList();
            combined.addAll(elA);
            combined.addAll(elB);
            return combined;
        }

        /**
         * Merger for fields of class {@link String}
         *
         * @param elA The preferred input
         * @param elB The non-preferred input
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.CharSequenceField"},
                comments = "Related component claim: mergerAddFieldMergerMethod"
        )
        private static final String mergeString(@Nullable final String elA, @Nullable final String elB) {
            return (Objects.nonNull(elA) && Objects.nonNull(elA.toString()) && (!elA.toString().isBlank())) ? elA : elB;
        }
    }

    /**
     * Interface for a record that can be merged with itself.
     * <p>
     * Intended merge process is that, for each field:
     * <ol>
     * <li>If both of the two instances have a null value, then the result is null</li>
     * <li>If one of the two instances has a null value, then take the non-null value</li>
     * <li>If both are non-null, and the field is itself can be merged, then merge the values using the other merger</li>
     * <li>If both are non-null, and the field is a collection, then union the collections</li>
     * <li>Otherwise, keep the value in this instance (instead of the one in the other instance)</li>
     * </ol>
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
            comments = "Related class claim: mergerStaticClass"
    )
    interface Mergeable extends _MatchingInterface {
        /**
         * Merge the current instance into the other instance, if it is present
         * @return The result of the merge
         *
         * @param other The element to merge into this one, if it is present
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.iface.MergeOptionalMethod"},
                comments = "Related class claim: mergeInterfaceMergeOptionalMethod"
        )
        default Person merge(@NonNull final Optional<Person> other) {
            Objects.requireNonNull(other, "You cannot supply a null Optional parameter");
            return other.map(oth -> this.merge(oth)).orElse(this.merge((Person) null));
        }

        /**
         * Merge the current instance into the other instance.
         * @return The result of the merge
         *
         * @param other The element to merge into this one
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.iface.MergeMethod"},
                comments = "Related class claim: mergeInterfaceMergeMethod"
        )
        default Person merge(@Nullable final Person other) {
            final var optOther = Optional.ofNullable(other);
            return Builder.builder()
                    .name(_MergerUtils.mergeString(this.name(), optOther.map(Person::name).orElse(null)))
                    .age(_MergerUtils.mergeInteger(this.age(), optOther.map(Person::age).orElse(null)))
                    .favouriteColours(_MergerUtils.mergeListString(this.favouriteColours(), optOther.map(Person::favouriteColours).orElse(null)))
                    .build();
        }
    }
}

And then you can "merge" instances of records together:

Sample method body
Person missingName = PersonUtils.builder().age(23).favouriteColours(List.of("red", "violet")).build();
Person missingAge = PersonUtils.builder().name("Zack").addFavouriteColour("blue").build();

Person allTogether = missingName.merge(missingAge);

// Which would be the same as doing:
Person isTheSameAs = PersonUtils.builder().age(23).favouriteColours(List.of("red", "violet", "blue")).name("Zack").build();

3.1.5. Differ

The "differ" allows you to compute the difference between two instances of a record. The processor creates a new class that contains details about the difference between the two instances field by field, including:

  • Has the field changed?

    • What was the original value?

    • What was the updated value?

  • If the field was a collection you can obtain an order-agnostic sub-diff of the contents that were:

    • Added

    • Removed

    • In common between the two

  • If the field is also diffable (i.e. another record that has a *Utils class with a differ) then a diff of that field can also be obtained

To enable the generation of a Diffable interface, enable the diffable setting. To use, implement the All (or Diffable) interface like so:

Person.java
@AdvancedRecordUtils(diffable = true)
public record Person(String name, int age, List<String> favouriteColours) implements PersonUtils.All {}
Generated Utils subclasses
public final class PersonUtils implements GeneratedUtil {
    /**
     * The result of a diff between two instances of {@link Person}
     * <p>
     * Results are pre-computed and stored in memory
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.DiffFactory"},
            comments = "Related class claim: differResult"
    )
    public static final class DiffOfPerson {
        /**
         * Has any field changed in this diff?
         */
        private final boolean __overallChanged;

        /**
         * Has the field named $S changed?
         */
        private final boolean age;

        /**
         * Diff of the field named $S
         */
        private final ListString favouriteColours;

        /**
         * Has the field named $S changed?
         */
        private final boolean name;

        /**
         * The original field named $S
         */
        @Nullable
        private final int originalAge;

        /**
         * The original field named $S
         */
        @Nullable
        private final List<String> originalFavouriteColours;

        /**
         * The original field named $S
         */
        @Nullable
        private final String originalName;

        /**
         * The (potentially) updated field named $S
         */
        @Nullable
        private final int updatedAge;

        /**
         * The (potentially) updated field named $S
         */
        @Nullable
        private final List<String> updatedFavouriteColours;

        /**
         * The (potentially) updated field named $S
         */
        @Nullable
        private final String updatedName;

        /**
         * Creates a new diff between two instances of {@link Diffable}
         * <p>
         * Results are pre-computed and stored in memory
         *
         * @param original The originating element of the diff
         * @param updated The (potentially) changed element of the diff
         */
        public DiffOfPerson(final Diffable original, final Diffable updated) {
            Objects.requireNonNull(original, "The originating element cannot be null");
            Objects.requireNonNull(updated, "The (potentially) changed element cannot be null");
            // "Computing the diff of " + "name" + " by calling Objects.equals"
            this.name = _DifferUtils.hasStringChanged(original.name(), updated.name());
            this.originalName = original.name();
            this.updatedName = updated.name();
            // "Computing the diff of " + "age" + " by calling Objects.equals"
            this.age = _DifferUtils.hasintChanged(original.age(), updated.age());
            this.originalAge = original.age();
            this.updatedAge = updated.age();
            // "Computing the diff of " + "favouriteColours" + " by using their generated diff utils"
            this.favouriteColours = _DifferUtils.hasListStringChanged(original.favouriteColours(), updated.favouriteColours());
            this.originalFavouriteColours = original.favouriteColours();
            this.updatedFavouriteColours = updated.favouriteColours();
            this.__overallChanged = this.hasNameChanged() || this.hasAgeChanged() || this.hasFavouriteColoursChanged() || false;
        }

        /**
         * Creates a new diff between two instances of {@link Person}
         * <p>
         * Results are pre-computed and stored in memory
         *
         * @param original The originating element of the diff
         * @param updated The (potentially) changed element of the diff
         */
        DiffOfPerson(final Person original, final Person updated) {
            Objects.requireNonNull(original, "The originating element cannot be null");
            Objects.requireNonNull(updated, "The (potentially) changed element cannot be null");
            // "Computing the diff of " + "name" + " by calling Objects.equals"
            this.name = _DifferUtils.hasStringChanged(original.name(), updated.name());
            this.originalName = original.name();
            this.updatedName = updated.name();
            // "Computing the diff of " + "age" + " by calling Objects.equals"
            this.age = _DifferUtils.hasintChanged(original.age(), updated.age());
            this.originalAge = original.age();
            this.updatedAge = updated.age();
            // "Computing the diff of " + "favouriteColours" + " by using their generated diff utils"
            this.favouriteColours = _DifferUtils.hasListStringChanged(original.favouriteColours(), updated.favouriteColours());
            this.originalFavouriteColours = original.favouriteColours();
            this.updatedFavouriteColours = updated.favouriteColours();
            this.__overallChanged = this.hasNameChanged() || this.hasAgeChanged() || this.hasFavouriteColoursChanged() || false;
        }

        /**
         * Obtains the diff of the "favouriteColours" field
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.EagerCollectionDiffResultCreator"},
                comments = "Related component claim: differComputation"
        )
        public final ListString diffFavouriteColours() {
            return this.favouriteColours;
        }

        /**
         * Has the "age" field changed between the two versions?
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.EagerStandardFieldChanged"},
                comments = "Related component claim: differComputation"
        )
        public final boolean hasAgeChanged() {
            return this.age;
        }

        /**
         * Has any field changed in this diff?
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.EagerStandardHasChangedCheck"},
                comments = "Related class claim: differGlobalHasChanged"
        )
        public final boolean hasChanged() {
            return this.__overallChanged;
        }

        /**
         * Has the "favouriteColours" field changed between the two versions?
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.EagerCollectionDiffResultCreator"},
                comments = "Related component claim: differComputation"
        )
        public final boolean hasFavouriteColoursChanged() {
            // If there are no added elements and no removed elements, nothing has changed
            return !(this.favouriteColours.addedElements().isEmpty() && this.favouriteColours.removedElements().isEmpty());
        }

        /**
         * Has the "name" field changed between the two versions?
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.EagerStandardFieldChanged"},
                comments = "Related component claim: differComputation"
        )
        public final boolean hasNameChanged() {
            return this.name;
        }

        /**
         * Return the original value for "age"
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.ValueHolder"},
                comments = "Related component claim: differValueHolding"
        )
        public final int originalAge() {
            return this.originalAge;
        }

        /**
         * Return the original value for "favouriteColours"
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.ValueHolder"},
                comments = "Related component claim: differValueHolding"
        )
        public final List<String> originalFavouriteColours() {
            return this.originalFavouriteColours;
        }

        /**
         * Return the original value for "name"
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.ValueHolder"},
                comments = "Related component claim: differValueHolding"
        )
        public final String originalName() {
            return this.originalName;
        }

        /**
         * Return the non-original value for "age"
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.ValueHolder"},
                comments = "Related component claim: differValueHolding"
        )
        public final int updatedAge() {
            return this.updatedAge;
        }

        /**
         * Return the non-original value for "favouriteColours"
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.ValueHolder"},
                comments = "Related component claim: differValueHolding"
        )
        public final List<String> updatedFavouriteColours() {
            return this.updatedFavouriteColours;
        }

        /**
         * Return the non-original value for "name"
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.ValueHolder"},
                comments = "Related component claim: differValueHolding"
        )
        public final String updatedName() {
            return this.updatedName;
        }

        /**
         * A record containing the difference between two collections
         *
         * @param addedElements The elements added to the collection
         * @param elementsInCommon The elements in common between the two instances
         * @param removedElements The elements removed from the collection
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.utils.CollectionDiffCreation"},
                comments = "Related component claim: differUtilsComputation"
        )
        public record ListString(
                List<String> addedElements,
                List<String> elementsInCommon,
                List<String> removedElements
        ) {
        }
    }

    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.DiffFactory"},
            comments = "Related class claim: differUtils"
    )
    public static final class _DifferUtils {
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.DiffFactory"},
                comments = "Related class claim: differUtils"
        )
        private _DifferUtils() {
            throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
        }

        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.utils.CollectionDiffCreation"},
                comments = "Related component claim: differUtilsComputation"
        )
        public static final DiffOfPerson.ListString hasListStringChanged(@Nullable final List<String> original,
                @Nullable final List<String> updated) {
            // Create frequency maps to count occurrences
            final Map<String, Long> originalFreq = Objects.requireNonNullElse(original, List.<String>of()).stream()
                        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
            final Map<String, Long> updatedFreq = Objects.requireNonNullElse(updated, List.<String>of()).stream()
                        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
            final List<String> added = new ArrayList<>();
            final List<String> removed = new ArrayList<>();
            final List<String> common = new ArrayList<>();
            // Obtain unique elements
            final Set<String> allUniqueElements = new HashSet<>(originalFreq.keySet());
            allUniqueElements.addAll(updatedFreq.keySet());
            for (final String element : allUniqueElements) {
                final long originalCount = originalFreq.getOrDefault(element, 0L);
                final long updatedCount = updatedFreq.getOrDefault(element, 0L);
                final long commonCount = Math.min(originalCount, updatedCount);
                for (long i = 0; i < commonCount; i++) {
                    common.add(element);
                }
                if (originalCount > updatedCount) {
                    final long removedCount = originalCount - updatedCount;
                    for (long i = 0; i < removedCount; i++) {
                        removed.add(element);
                    }
                }
                if (updatedCount > originalCount) {
                    final long addedCount = updatedCount - originalCount;
                    for (long i = 0; i < addedCount; i++) {
                        added.add(element);
                    }
                }
            }
            return new DiffOfPerson.ListString(List.copyOf(added), List.copyOf(common), List.copyOf(removed));
        }

        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.utils.BasicHasChangedCheck"},
                comments = "Related component claim: differUtilsComputation"
        )
        public static final boolean hasStringChanged(@Nullable final String original, @Nullable final String updated) {
            return !Objects.equals(original, updated);
        }

        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.utils.BasicHasChangedCheck"},
                comments = "Related component claim: differUtilsComputation"
        )
        public static final boolean hasintChanged(@Nullable final int original, @Nullable final int updated) {
            return !Objects.equals(original, updated);
        }
    }

    /**
     * Interface for a record that can compute differences against another instance of the same type
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.DiffFactory"},
            comments = "Related class claim: differInterface"
    )
    interface Diffable extends _MatchingInterface {
        /**
         * Generate the diff between this instance ("original") and the provided instance ("updated")
         * <p>
         * Diff is computed as soon as this method is called
         * @return The result of the diff
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.DiffFactory"},
                comments = "Related class claim: differInterface"
        )
        default DiffOfPerson diff(@NonNull final Diffable updated) {
            return new DiffOfPerson(this, updated);
        }
    }
}

And then you can compute the difference between two instances:

Sample method body
Person personA = PersonUtils.builder()
    .name("Aerith")
    .addFavouriteColours("red")
    .addFavouriteColours("pink")
    .build();
Person personB = PersonUtils.builder()
    .name("Tifa")
    .favouriteColours(List.of("purple", "teal"))
    .build();

PersonUtils.DiffOfPerson diff = personA.diff(personB);
if (diff.hasAgeChanged()) {
    // Do something
}
if (diff.diffFavouriteColours().addedElements().isEmpty()) {
    // Do something
}

3.1.6. XML

The XML utility allows you to serialise a record to XML. At the moment, only serialisation is supported.

Using this utility requires you to have jakarta.xml.bind-api on the classpath as you will need to use the annotations from that library to control the output. It also requires the JDK provided XML StAX utilities (no dependency), which are part of the java.xml module.

To enable the generation of an XML interface, enable the xmlable setting and mark your record fields with the required annotations. To use, implement the All (or XML) interface like so:

Person.java
@AdvancedRecordUtils(xmlable = true)
public record Person(
    @XmlElement(name = "Name")
    String name,
    @XmlAttribute(name = "Age")
    int age,
    @XmlTransient
    List<String> favouriteColours
) implements PersonUtils.All {
}
By default, you must annotate each component. If a required annotation is missing, the processor fails compilation, and explains what to add. To exclude a component, use @XmlTransient (or Jackson’s or Avaje Jsonb’s ignore annotation).
One of the settings (inferXmlElementName) gives you the option to have the XmlElement annotation "inferred" for you so you don’t have to annotate those fields.

The processor will automatically handle Optional (including OptionalInt, OptionalDouble, and OptionalLong) as well as Collection items (like List and Set), and will treat empty values the same as null values.

The processor also handles interfaces, assuming it knows the implementing records.

Generated Utils subclasses
public final class PersonUtils implements GeneratedUtil {
    @NullUnmarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.XmlStaticClassGenerator"},
            comments = "Related class claim: xmlStaticClass"
    )
    public static final class _XmlUtils {
        private static final Optional<String> __DEFAULT_NAMESPACE_URI = Optional.empty();

        private static final String __DEFAULT_TAG_NAME = "Person";

        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.XmlStaticClassGenerator"},
                comments = "Related class claim: xmlStaticClass"
        )
        private _XmlUtils() {
            throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
        }

        /**
         * Write out the class to the requested {@link XMLStreamWriter}, referring to itself with the reqested tag name
         * <p>
         * This method will close any tags it opens. It is not expected that it will start or end the document.
         *
         * @param el The item to write to XML
         * @param output The output to write to
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.utils.ToXmlMethodNoTag"},
                comments = "Related class claim: xmlStaticClassToXmlNoTag"
        )
        public static void writeToXml(@NonNull final Person el, @NonNull final XMLStreamWriter output) throws XMLStreamException {
            _XmlUtils.writeToXml(el, output, null, null, null);
        }

        /**
         * Write out the class to the requested {@link XMLStreamWriter}, referring to itself with the reqested tag name
         * <p>
         * This method will close any tags it opens. It is not expected that it will start or end the document.
         *
         * @param el The item to write to XML
         * @param output The output to write to
         * @param requestedTagName The tag name requested for this element. If null, will use the default tag name
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.utils.ToXmlMethodNoNamespace"},
                comments = "Related class claim: xmlStaticClassToXmlNoNS"
        )
        public static void writeToXml(@NonNull final Person el, @NonNull final XMLStreamWriter output, final String requestedTagName) throws
                XMLStreamException {
            _XmlUtils.writeToXml(el, output, requestedTagName, null, null);
        }

        /**
         * Write out the class to the requested {@link XMLStreamWriter}, referring to itself with the reqested tag name
         * <p>
         * This method will close any tags it opens. It is not expected that it will start or end the document.
         *
         * @param el The item to write to XML
         * @param output The output to write to
         * @param requestedTagName The tag name requested for this element. If null, will use the default tag name
         * @param requestedNamespace The namespace requested for this element. If null, will use the default namespace (NOT the one on the element)
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.utils.ToXmlMethodNoDefNamespace"},
                comments = "Related class claim: xmlStaticClassToXmlNoDefNS"
        )
        public static void writeToXml(@NonNull final Person el, @NonNull final XMLStreamWriter output, final String requestedTagName,
                final String requestedNamespace) throws XMLStreamException {
            _XmlUtils.writeToXml(el, output, requestedTagName, requestedNamespace, null);
        }

        /**
         * Write out the provided instance to the requested {@link XMLStreamWriter}, referring to itself with the reqested tag name
         * <p>
         * This method will close any tags it opens. It is not expected that it will start or end the document.
         *
         * @param el The item to write to XML
         * @param output The output to write to
         * @param requestedTagName The tag name requested for this element. If null, will use the default tag name
         * @param requestedNamespace The namespace requested for this element. If null, will use the default namespace (NOT the one on the element)
         * @param currentDefaultNamespace The current default namespace
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.utils.WriteStaticToXml"},
                comments = "Related class claim: xmlStaticClassToXml"
        )
        public static void writeToXml(@NonNull final Person el, @NonNull final XMLStreamWriter output, final String requestedTagName,
                final String requestedNamespace, final String currentDefaultNamespace) throws XMLStreamException {
            Objects.requireNonNull(el, "Cannot supply null element to be written to XML");
            Objects.requireNonNull(output, "Cannot supply null output for XML content");
            final String tag = _XmlUtils.createTag(requestedTagName);
            final Optional<String> namespace = _XmlUtils.createNamespace(requestedNamespace, currentDefaultNamespace);
            final Optional<String> defNs = _XmlUtils.__DEFAULT_NAMESPACE_URI.filter(ignored -> Objects.nonNull(requestedNamespace) && (!requestedNamespace.isBlank()));
            if (defNs.isPresent()) {
                output.setDefaultNamespace(defNs.get());
            }
            final @Nullable String namespaceToPassDown = defNs.orElse(currentDefaultNamespace);
            if (namespace.isPresent()) {
                output.writeStartElement(namespace.get(), tag);
            } else {
                output.writeStartElement(tag);
            }
            // The write order is as follows:
            //  1. Attributes in the propOrder if present
            //  2. Remaining attributes in declaration order
            //  3. Element or Elements in the propOrder if present
            //  4. Element or Elements in declaration order
            _XmlUtils.age(output, el.age(), namespaceToPassDown);
            _XmlUtils.name(output, el.name(), namespaceToPassDown);
            output.writeEndElement();
        }

        /**
         * Add the {@code age} field to the XML output
         *
         * @param output The output to write to
         * @param val The item to write
         * @param currentDefaultNamespace The current default namespace
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.utils.attribute.WritePrimitiveInt"},
                comments = "Related component claim: xmlWriteField"
        )
        private static final void age(@NonNull final XMLStreamWriter output, @Nullable final int val, @Nullable final String currentDefaultNamespace)
                throws XMLStreamException {
            output.writeAttribute("Age", String.valueOf(val));
        }

        /**
         * Determine the final namespace of the current XmlType
         *
         * @param requestedNamespace The requested namespace when called
         * @param currentDefaultNamespace The currently available namespace
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.utils.WriteStaticToXml"},
                comments = "Related class claim: xmlStaticClassToXml"
        )
        private static final Optional<String> createNamespace(@Nullable final String requestedNamespace,
                @Nullable final String currentDefaultNamespace) {
            return Optional.ofNullable(requestedNamespace)
                        .filter(Objects::nonNull)
                        .filter(Predicate.not(String::isBlank))
                        .filter(x -> !AdvancedRecordUtilsGenerated.XML_DEFAULT_STRING.equals(x))
                        .or(() -> __DEFAULT_NAMESPACE_URI)
                        .or(
                            () -> Optional.ofNullable(currentDefaultNamespace)
                                .filter(Objects::nonNull)
                                .filter(Predicate.not(String::isBlank))
                                .filter(x -> !AdvancedRecordUtilsGenerated.XML_DEFAULT_STRING.equals(x))
                        );
        }

        /**
         * Determine the tag to write out for the current XML Element
         *
         * @param incomingTag The incoming tag that was requested
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.utils.WriteStaticToXml"},
                comments = "Related class claim: xmlStaticClassToXml"
        )
        private static final String createTag(final String incomingTag) {
            return Optional.ofNullable(incomingTag)
                            .filter(Objects::nonNull)
                            .filter(Predicate.not(String::isBlank))
                            .filter(x -> !AdvancedRecordUtilsGenerated.XML_DEFAULT_STRING.equals(x))
                            .orElse(__DEFAULT_TAG_NAME);
        }

        /**
         * Add the {@code name} field to the XML output
         *
         * @param output The output to write to
         * @param val The item to write
         * @param currentDefaultNamespace The current default namespace
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.utils.elements.noncollections.WriteCharSequence"},
                comments = "Related component claim: xmlWriteField"
        )
        private static final void name(@NonNull final XMLStreamWriter output, @Nullable final CharSequence val,
                @Nullable final String currentDefaultNamespace) throws XMLStreamException {
            if (Objects.nonNull(val) && Objects.nonNull(val.toString()) && (!val.toString().isBlank()) ) {
                output.writeStartElement("Name");
                output.writeCharacters(val.toString());
                output.writeEndElement();
            }
        }
    }

    /**
     * Provides the ability for a class to convert itself into XML
     */
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.XmlInterfaceGenerator"},
            comments = "Related class claim: xmlInterface"
    )
    interface XML extends _MatchingInterface {
        /**
         * Write out the class to the requested {@link XMLStreamWriter}, referring to itself with the reqested tag name
         * <p>
         * This method will close any tags it opens. It is not expected that it will start or end the document.
         *
         * @param output The output to write to
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.iface.ToXmlMethodNoTag"},
                comments = "Related class claim: xmlInterfaceToXmlNoTag"
        )
        default void writeSelfTo(@NonNull final XMLStreamWriter output) throws XMLStreamException {
            this.writeSelfTo(output, null, null, null);
        }

        /**
         * Write out the class to the requested {@link XMLStreamWriter}, referring to itself with the reqested tag name
         * <p>
         * This method will close any tags it opens. It is not expected that it will start or end the document.
         *
         * @param output The output to write to
         * @param requestedTagName The tag name requested for this element. If null, will use the default tag name
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.iface.ToXmlMethodNoNamespace"},
                comments = "Related class claim: xmlInterfaceToXmlNoNs"
        )
        default void writeSelfTo(@NonNull final XMLStreamWriter output, @Nullable final String requestedTagName) throws XMLStreamException {
            this.writeSelfTo(output, requestedTagName, null, null);
        }

        /**
         * Write out the class to the requested {@link XMLStreamWriter}, referring to itself with the reqested tag name
         * <p>
         * This method will close any tags it opens. It is not expected that it will start or end the document.
         *
         * @param output The output to write to
         * @param requestedTagName The tag name requested for this element. If null, will use the default tag name
         * @param requestedNamespace The namespace requested for this element. If null, will use the default namespace (NOT the one on the element)
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.iface.ToXmlMethodNoDefNamespace"},
                comments = "Related class claim: xmlInterfaceToXmlNoDefNs"
        )
        default void writeSelfTo(@NonNull final XMLStreamWriter output, @Nullable final String requestedTagName,
                @Nullable final String requestedNamespace) throws XMLStreamException {
            this.writeSelfTo(output, requestedTagName, requestedNamespace, null);
        }

        /**
         * Write out the current instance to the requested {@link XMLStreamWriter}, referring to itself with the reqested tag name
         * <p>
         * This method will close any tags it opens. It is not expected that it will start or end the document.
         *
         * @param output The output to write to
         * @param requestedTagName The tag name requested for this element. If null, will use the default tag name
         * @param requestedNamespace The namespace requested for this element. If null, will use the default namespace (NOT the one on the element)
         * @param currentDefaultNamespace The current default namespace
         */
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.iface.WriteIfaceToXml"},
                comments = "Related class claim: xmlInterfaceToXml"
        )
        default void writeSelfTo(@NonNull final XMLStreamWriter output, @Nullable final String requestedTagName,
                @Nullable final String requestedNamespace, final String currentDefaultNamespace) throws XMLStreamException {
            Objects.requireNonNull(output, "Cannot supply null output for XML content");
            final String tag = _XmlUtils.createTag(requestedTagName);
            final Optional<String> namespace = _XmlUtils.createNamespace(requestedNamespace, currentDefaultNamespace);
            final Optional<String> defNs = _XmlUtils.__DEFAULT_NAMESPACE_URI.filter(ignored -> Objects.isNull(requestedNamespace) || (requestedNamespace.isBlank()));
            if (defNs.isPresent()) {
                output.setDefaultNamespace(defNs.get());
            }
            final @Nullable String namespaceToPassDown = defNs.orElse(currentDefaultNamespace);
            if (namespace.isPresent()) {
                output.writeStartElement(namespace.get(), tag);
            } else {
                output.writeStartElement(tag);
            }
            // The write order is as follows:
            //  1. Attributes in the propOrder if present
            //  2. Remaining attributes in declaration order
            //  3. Element or Elements in the propOrder if present
            //  4. Element or Elements in declaration order
            _XmlUtils.age(output, this.age(), namespaceToPassDown);
            _XmlUtils.name(output, this.name(), namespaceToPassDown);
            output.writeEndElement();
        }
    }
}

And then you can convert an instance into XML:

Sample method body
// You will need to handle the XMLStreamException that can be thrown in this example
final XMLStreamWriter streamWriter = createXmlWriter();
streamWriter.writeStartDocument();
// personA from earlier example
personA.writeSelfTo(streamWriter);
streamWriter.writeEndDocument();

There are multiple writeSelfTo methods generated:

  • One that takes just the output

  • One that takes the output and an override for the tag name

  • One that takes the output, an override for the tag name, and an override for the namespace

  • One that takes the output, and overrides for the tag name, the namespace, and the current default namespace

The defaults are based on available annotations (such as XmlType) if they are present, before finally defaulting to the name of the record (in this case, Person).

The order in which components are written is:

  1. All @XmlAttribute components that appear in the propOrder (see below @XmlType)

  2. All other @XmlAttribute components as per the @XmlAccessorOrder (see below)

  3. All @XmlElement components that appear in the propOrder

  4. All other @XmlElement components as per the @XmlAccessorOrder

We support the following annotations:

Component annotations
@XmlElement

Indicates that a component of a record should be turned into an XML Element.

There is a setting that allows the processor to pretend that un-annotated components in records are annotated with @XmlElement. See inferXmlElementName for details.
Usage: Place on a component of a record.
public record Person(
    @XmlElement
    String name
) {}
Alternate Usage: Place on an accessor method defined on an interface.
public interface PersonIface {
    @XmlElement
    String name();
}

public record Person(
    String name
) implements PersonIface {}

We support the following fields:

  • name - the name of the written element

  • namespace - the namespace of the written element

  • defaultValue - the default value to be written into the element if none is defined

  • required - indicates that the field is required, and throw an exception if it’s not defined

See the Javadoc for XmlElement for more details

Generated code varies depending on the kind of the record component, if it is required or not, and if there is a default specified. Generally speaking, the processor converts the component value into a sensible string value and outputs that. The methods are named after the component.
Generated code for an OffsetDateTime field (@XmlElement(name = "offsetDateTimeElement"))
public final class ExampleUtils implements GeneratedUtil {
    public static final class _XmlUtils {
        private static final void offsetDateTimeElement(@NonNull final XMLStreamWriter output, @Nullable final OffsetDateTime val,
                @Nullable final String currentDefaultNamespace) throws XMLStreamException {
            if (Objects.nonNull(val)) {
                output.writeStartElement("offsetDateTimeElement");
                output.writeCharacters(val.atZoneSameInstant(ZoneOffset.UTC).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
                output.writeEndElement();
            }
        }
    }
}
Generated code for a String field (@XmlElement(name = "stringElement"))
public final class ExampleUtils implements GeneratedUtil {
    public static final class _XmlUtils {
        private static final void stringElement(@NonNull final XMLStreamWriter output, @Nullable final CharSequence val,
                @Nullable final String currentDefaultNamespace) throws XMLStreamException {
            if (Objects.nonNull(val) && StringUtils.isNotBlank(val.toString())) {
                output.writeStartElement("stringElement");
                output.writeCharacters(val.toString());
                output.writeEndElement();
            }
        }
    }
}
Generated code for an Integer field with a default (@XmlElement(name = "boxedIntElementDefault", default = "1"))
public final class ExampleUtils implements GeneratedUtil {
    public static final class _XmlUtils {
        private static final void boxedIntElementDefault(@NonNull final XMLStreamWriter output, @Nullable final Integer val,
                @Nullable final String currentDefaultNamespace) throws XMLStreamException {
            output.writeStartElement("boxedIntElementDefault");
            if(Objects.nonNull(val)) {
                output.writeCharacters(val.toString());
            } else {
                // Supplied value for boxedIntElementDefault (element name boxedIntElementDefault) was null/blank, writing default of 1
                output.writeCharacters("1");
            }
            output.writeEndElement();
        }
    }
}
Generated code for an Integer field that is required (@XmlElement(name = "boxedIntElementRequired", required = true))
public final class ExampleUtils implements GeneratedUtil {
    public static final class _XmlUtils {
        private static final void boxedIntElementRequired(@NonNull final XMLStreamWriter output, @Nullable final Integer val,
                @Nullable final String currentDefaultNamespace) throws XMLStreamException {
            Objects.requireNonNull(val, "Element is marked as required - cannot provide a null/blank value for field boxedIntElementRequired (element name: boxedIntElementRequired)");
            output.writeStartElement("boxedIntElementRequired");
            output.writeCharacters(val.toString());
            output.writeEndElement();
        }
    }
}
Generated code for another record (@XmlElement(name = "MyOtherRecord"))
public final class ExampleUtils implements GeneratedUtil {
    public static final class _XmlUtils {
        private static final void someOtherRecord(@NonNull final XMLStreamWriter output, @Nullable final OtherRecord val,
                @Nullable final String currentDefaultNamespace) throws XMLStreamException {
            if (Objects.nonNull(val)) {
                OtherRecordUtils._XmlUtils.writeToXml(val, output, "MyOtherRecord", null, currentDefaultNamespace);
            }
        }
    }
}
@XmlAttribute

Indicates that a component of a record should be turned into an XML Attribute.

Usage: Place on a component of a record.
public record Person(
    @XmlAttribute
    String name
) {}
Alternate Usage: Place on an accessor method defined on an interface.
public interface PersonIface {
    @XmlAttribute
    String name();
}

public record Person(
    String name
) implements PersonIface {}

We support the following options:

  • name - the name of the written attribute

  • namespace - the namespace of the written attribute

  • required - indicates that the field is required, and throw an exception if it’s not defined

See the Javadoc for XmlAttribute for more details

Generated code varies depending on the kind of the record component, and if it is required or not. Generally speaking, the processor converts the component value into a sensible string value and outputs that. The methods are named after the component.
Generated code for a primitive long field (@XmlAttribute(name = "primitiveLongAttribute"))
public final class ExampleUtils implements GeneratedUtil {
    public static final class _XmlUtils {
        private static final void primitiveLongAttribute(@NonNull final XMLStreamWriter output, @Nullable final long val,
                @Nullable final String currentDefaultNamespace) throws XMLStreamException {
            output.writeAttribute("primitiveLongAttribute", String.valueOf(val));
        }
    }
}
@XmlTransient

Indicates that a component of a record should be skipped when converting to XML.

You can also use Jackson or Avaje Jsonb annotations ignore annotations - the processor will "infer" that the component is annotated with @XmlTransient
Usage: Place on a component of a record.
public record Person(
    @XmlTransient
    String name
) {}
Alternate Usage: Place on an accessor method defined on an interface.
public interface PersonIface {
    @XmlTransient
    String name();
}

public record Person(
    String name
) implements PersonIface {}

See the Javadoc for XmlTransient for more details

@XmlElements

Indicates that a component of a record that points at an interface should be turned into different elements based on the implementing type.

Usage: Place on a component of a record.
public record MyRecord(
    @XmlElements({
        @XmlElement(type = ImplementationOne.class, name = "ImplementationA"),
        @XmlElement(type = ImplementationTwo.class),
    })
    SomeInterface someInterface
) {}
Alternate Usage: Place on an accessor method defined on an interface.
public interface AnIface {
    @XmlElements({
        @XmlElement(type = ImplementationOne.class, name = "ImplementationA"),
        @XmlElement(type = ImplementationTwo.class),
    })
    SomeInterface someInterface();
}

public record MyRecord(
    SomeInterface someInterface
) implements AnIface {}

We support the following fields:

  • name - the name of the written element

  • namespace - the namespace of the written element

  • type - the target type of the interface

See the Javadoc for XmlElements for more details

Generated code
public final class ExampleUtils implements GeneratedUtil {
    public static final class _XmlUtils {
        private static final void someValue(@NonNull final XMLStreamWriter output, @Nullable final ThirdLevelInterface val,
                @Nullable final String currentDefaultNamespace) throws XMLStreamException {
            if (Objects.isNull(val)) {
                return;
            } else if (val instanceof ThirdLevelAFromA tVal) {
                ThirdLevelAFromAUtils._XmlUtils.writeToXml(tVal, output, "ThirdLevelAFromA", null, currentDefaultNamespace);
            } else if (val instanceof ThirdLevelBFromA tVal) {
                ThirdLevelBFromAUtils._XmlUtils.writeToXml(tVal, output, "someValueB", null, currentDefaultNamespace);
            } else if (val instanceof ThirdLevelCFromA tVal) {
                ThirdLevelCFromAUtils._XmlUtils.writeToXml(tVal, output, "someValueC", null, currentDefaultNamespace);
            }
        }
    }
}
@XmlElements is inferred if there are other ways in which the processor can find implementing records. When doing this, the processor uses the calculated defaults for the name and namespace of the element.
@XmlElementWrapper

Indicates that a collection component of a record that is annotated with either @XmlElement or @XmlElements (or if they are inferred) should be wrapped in another element when converting to XML.

Usage: Place on a collection component of a record.
public record Address(
    @XmlElementWrapper(name = "Components")
    @XmlElement(name = "Component")
    List<String> component
) {}
Alternate Usage: Place on an accessor method defined on an interface.
public interface AddressIface {
    @XmlElementWrapper(name = "Components")
    @XmlElement(name = "Component")
    List<String> component();
}

public record Address(
    List<String> component
) implements AddressIface {}

We support the following options:

  • name - the name of the written wrapper

  • namespace - the namespace of the written wrapper

See the Javadoc for XmlElementWrapper for more details

Generated code

This is using the Eclipse Collection ImmutableList, but will work with any Java Collection type.

public final class ExampleUtils implements GeneratedUtil {
    public static final class _XmlUtils {
        private static final void someItemsAsElements(@NonNull final XMLStreamWriter output, @Nullable final ImmutableList<String> val,
                @Nullable final String currentDefaultNamespace) throws XMLStreamException {
            if (Objects.nonNull(val)) {
                if (!val.isEmpty()) {
                    output.writeStartElement("SomeElementStrings");
                    for (final CharSequence __innerValue : val) {
                        if(Objects.isNull(__innerValue)) {
                            continue;
                        }
                        output.writeStartElement("SomeElementString");
                        output.writeCharacters(__innerValue.toString());
                        output.writeEndElement();
                    }
                    output.writeEndElement();
                }
            }
        }
    }
}
Interface annotations
@XmlSeeAlso

Lists the implementing records of an interface

Usage: Place on an interface.
@XmlSeeAlso({ImplementationOne.class, ImplementationTwo.class})
public interface MyInterface {}

See the Javadoc for XmlSeeAlso for more details

Record annotations
@XmlType

Defines options for the serialisation of a record.

Usage: Place on a record.
@XmlType
public record Person(
    String name
) {}

We support the following fields:

  • name - the default name for the record when being serialised

  • namespace - the default namespace for the record when being serialised

  • propOrder - the names of components of the record, in the order in which they should be written

The propOrder must match the name of the component not the name of the element when serialised, as per the documentation of the annotation. In addition, all attributes will be written before all elements (although this will control the order within those categories)

See the Javadoc for XmlType for more details

@XmlRootElement

Defines options for the serialisation of the root of a tree of records

Usage: Place on a record.
@XmlRootElement
public record Person(
    String name
) {}

We support the following fields:

  • name - the default name for the record when being serialised

  • namespace - the default namespace for the record when being serialised and all child elements.

See the Javadoc for XmlRootElement for more details

The settings @XmlType take priority over the @XmlRootElement. However, the @XmlRootElement determines where to set the information from @XmlSchema (if applicable)
@XmlAccessorOrder

Defines the order in which the components are written.

Usage: Place on a record.
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
public record Person(
    String name,
    int age
) {}
Alternative Usage: Place on a package (in a file named package-info.java) to apply to all records in the package.
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
package org.example;
If applied to both a record and a package, the setting on the record will apply.

There are only two possible values:

  • UNDEFINED (Default) - The ordering of fields and properties in a record is undefined.

  • ALPHABETICAL - The ordering of fields and properties in a record is alphabetical by component name.

When ordering alphabetically, we sort on the name of the component not the name of the element when serialised, as per the documentation of the annotation. In addition, because we use the XMLStreamWriter, all attributes are written before all elements (although this will control the order within those categories).
The processor currently treats UNDEFINED as the order in which the components are defined in the record. However, strictly speaking, the specification doesn’t mandate this and therefore the order may be changed in a future major version.

See the Javadoc for XmlAccessorOrder for more details.

Package annotations
@XmlSchema

Defines information about the schemas for all records in the package

Usage: Place on a package.
@XmlSchema
package org.example;

We support the following fields:

  • namespace - sets the default namespace for the records in the package

  • xmlns - sets the namespace URI prefixes for the package

The settings from the @XmlSchema annotation are applied when processing records with @XmlRootElement annotations

See the Javadoc for XmlSchema for more details

@XmlAccessorOrder

Defines the order in which the components are written.

Usage: Place on a record.
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
public record Person(
    String name,
    int age
) {}
Alternative Usage: Place on a package (in a file named package-info.java) to apply to all records in the package.
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
package org.example;
If applied to both a record and a package, the setting on the record will apply.

There are only two possible values:

  • UNDEFINED (Default) - The ordering of fields and properties in a record is undefined.

  • ALPHABETICAL - The ordering of fields and properties in a record is alphabetical by component name.

When ordering alphabetically, we sort on the name of the component not the name of the element when serialised, as per the documentation of the annotation. In addition, because we use the XMLStreamWriter, all attributes are written before all elements (although this will control the order within those categories).
The processor currently treats UNDEFINED as the order in which the components are defined in the record. However, strictly speaking, the specification doesn’t mandate this and therefore the order may be changed in a future major version.

See the Javadoc for XmlAccessorOrder for more details.

3.2. Type Converter

By annotating a public static method with @TypeConverter, then any time that the return type of that method is a field of a record, the generated builder will also include a method that invokes the TypeConverter annotated method. This applies globally across all builders.

Because @TypeConverter applies globally, using the same permutation of return type and parameters more than once will cause compilation to fail. This includes @TypeConverter methods discovered via imports (see below for details on imports).

A good example of this is for enums. Given the following enum using the common pattern of having a label that we would like to be able to use:

AnEnumInDep.java
import io.github.cbarlin.aru.annotations.TypeConverter;

public enum AnEnumInDep {
    MONDAY("Monday"),
    TUESDAY("Tuesday");

    private final String label;

    AnEnumInDep(final String label) {
        this.label = label;
    }

    public String label() {
        return label;
    }

    @TypeConverter
    public static AnEnumInDep fromLabel(final String label) {
        if ("Monday".equals(label)) {
            return MONDAY;
        }
        return TUESDAY;
    }
}

Then if we have a record that uses that enum:

MyRecord.java
@AdvancedRecordUtils
public record MyRecord(
    AnEnumInDep andImAnEnum,
    AnEnumInDep theEnumAgain
) {}

Then you can implicitly use the fromLabel method on the builder of MyRecord like so:

Sample method body
final MyRecord myB = MyRecordUtils.builder()
    // This internally calls the method annotated with TypeConverter
    .andImAnEnum("Monday")
    // You can still pass in the item directly
    .theEnumAgain(AnEnumInDep.TUESDAY)
    .build();
Generated Builder Methods
/**
 * Updates the value of {@code andImAnEnum}
 * <p>
 * Supplying a null value will set the current value to null
 *
 * @param andImAnEnum The replacement value
 */
@NonNull
@Generated(
        value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
        comments = "Related component claim: builderPlainSetter"
)
public Builder andImAnEnum(@Nullable final AnEnumInDep andImAnEnum) {
    this.andImAnEnum = andImAnEnum;
    return this;
}

/**
 * Updates the value of {@code andImAnEnum}
 */
@Generated(
        value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.UsingTypeConverter"},
        comments = "Related component claim: builderUseTypeConverter"
)
public Builder andImAnEnum(final String label) {
    return this.andImAnEnum(AnEnumInDep.fromLabel(label));
}

This is not restricted to enums - you can translate any parameters to (almost) any return type and it’ll be picked up.

By default, the processor rejects methods that use extremely common types (e.g. String) that may "flood" generated builders. If you wish to do this anyway, you can add a permitReturnTypeWhichMayResultInTooManyMethods = true setting on the @TypeConverter

3.3. Cascading usage

In the event that you have records that reference other records that are in the same package or sub-package, the processor will automatically "cascade" down the "tree" and work on the referenced record without needing to annotate it. This can be extremely useful if you have a large tree of records, such as one that represents an XML schema.

This is the default behaviour, so as an example take the following records:

MyRecordA.java
public record MyRecordA(
    String someStringField,
    int someIntField
) {
}
MyRecordB.java
@AdvancedRecordUtils(merger = true)
public record MyRecordB(
    MyRecordA otherItem,
    MyRecordA woo,
    MyRecordB recursionFtw // The processor deals with self-referencing types
) {

}

Then both a MyRecordBUtils and a MyRecordAUtils are created, despite the MyRecordA not having an annotation!

Generated MyRecordAUtils
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;

/**
 * An auto-generated utility class to work with {@link MyRecordA} objects
 * <p>
 * This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
 * <p>
 * For more details, see the GitHub page for cbarlin/advanced-record-utils
 */
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
        generatedFor = MyRecordA.class,
        version = @AdvancedRecordUtilsGenerated.Version(
                major = 0,
                minor = 6,
                patch = 0
        ),
        settings = @AdvancedRecordUtils(
                merger = true
        ),
        internalUtils = {
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "All", implementation = MyRecordAUtils.All.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "Builder", implementation = MyRecordAUtils.Builder.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "Mergeable", implementation = MyRecordAUtils.Mergeable.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "With", implementation = MyRecordAUtils.With.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "_MatchingInterface", implementation = MyRecordAUtils._MatchingInterface.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "_MergerUtils", implementation = MyRecordAUtils._MergerUtils.class)
        },
        references = {

        },
        usedTypeConverters = {

        }
)
public final class MyRecordAUtils implements GeneratedUtil {
    /**
     * Create a blank builder of {@link MyRecordA}
     */
    @NonNull
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
            comments = "Related class claim: builderEmpty"
    )
    public static final Builder builder() {
        return Builder.builder();
    }

    /**
     * Creates a new builder of {@link MyRecordA} by copying an existing instance
     *
     * @param original The existing instance to copy
     */
    @NonNull
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
            comments = "Related class claim: builderCopy"
    )
    public static final Builder builder(final MyRecordA original) {
        return Builder.builder(original);
    }

    /**
     * A class used for building {@link MyRecordA} objects
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.BuilderClassCreatorVisitor"},
            comments = "Related class claim: builder"
    )
    public static final class Builder {
        @Nullable
        private int someIntField;

        @Nullable
        private String someStringField;

        /**
         * Create a blank builder of {@link MyRecordA}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
                comments = "Related class claim: builderEmpty"
        )
        public static final Builder builder() {
            return new Builder();
        }

        /**
         * Creates a new builder of {@link MyRecordA} by copying an existing instance
         *
         * @param original The existing instance to copy
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
                comments = "Related class claim: builderCopy"
        )
        public static final Builder builder(final MyRecordA original) {
            Objects.requireNonNull(original, "Cannot copy a null instance");
            // "Copying an existing instance"
            return Builder.builder()
                    .someStringField(original.someStringField())
                    .someIntField(original.someIntField());
        }

        /**
         * Creates a new instance of {@link MyRecordA} from the fields set on this builder
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddPlainBuild"},
                comments = "Related class claim: builderBuild"
        )
        public MyRecordA build() {
            // "Creating new instance"
            return new MyRecordA(
                    this.someStringField(),
                    	this.someIntField()
                    );
        }

        /**
         * Returns the current value of {@code someIntField}
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
                comments = "Related component claim: builderGetter"
        )
        public int someIntField() {
            return this.someIntField;
        }

        /**
         * Updates the value of {@code someIntField}
         * <p>
         * Supplying a null value will set the current value to null
         *
         * @param someIntField The replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
                comments = "Related component claim: builderPlainSetter"
        )
        public Builder someIntField(@Nullable final int someIntField) {
            this.someIntField = someIntField;
            return this;
        }

        /**
         * Returns the current value of {@code someStringField}
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
                comments = "Related component claim: builderGetter"
        )
        public String someStringField() {
            return this.someStringField;
        }

        /**
         * Updates the value of {@code someStringField}
         * <p>
         * Supplying a null value will set the current value to null
         *
         * @param someStringField The replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
                comments = "Related component claim: builderPlainSetter"
        )
        public Builder someStringField(@Nullable final String someStringField) {
            this.someStringField = someStringField;
            return this;
        }
    }

    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.AllInterfaceGenerator"},
            comments = "Related class claim: allIface"
    )
    public interface All extends Mergeable, With {
    }

    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
            comments = "Related class claim: mergerStaticClass"
    )
    public static final class _MergerUtils {
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
                comments = "Related class claim: mergerStaticClass"
        )
        private _MergerUtils() {
            throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
        }

        /**
         * Merge two instances of {@link MyRecordA} together
         *
         * @param preferred The preferred element
         * @param other The non-preferred element
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.MergeMethod"},
                comments = "Related class claim: mergeStaticMergeMethod"
        )
        public static MyRecordA merge(@Nullable final MyRecordA preferred, @Nullable final MyRecordA other) {
            if (Objects.isNull(other))  {
                // "Short-circuit of merge - other is null"
                return preferred;
            } else if (Objects.isNull(preferred)) {
                // "Short-circuit of merge - preferred is null"
                return other;
            }
            // "Merging two instances together"
            return Builder.builder()
                    .someStringField(_MergerUtils.mergeString(preferred.someStringField(), other.someStringField()))
                    .someIntField(_MergerUtils.mergeint(preferred.someIntField(), other.someIntField()))
                    .build();
        }

        /**
         * Merger for fields of class {@link String}
         *
         * @param elA The preferred input
         * @param elB The non-preferred input
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.CharSequenceField"},
                comments = "Related component claim: mergerAddFieldMergerMethod"
        )
        private static final String mergeString(@Nullable final String elA, @Nullable final String elB) {
            return (Objects.nonNull(elA) && Objects.nonNull(elA.toString()) && (!elA.toString().isBlank())) ? elA : elB;
        }

        /**
         * Merger for a primitive field
         *
         * @param elA The preferred input
         * @param elB The non-preferred input
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.Primitive"},
                comments = "Related component claim: mergerAddFieldMergerMethod"
        )
        private static final int mergeint(final int elA, final int elB) {
            // For primitives, we simply return the preferred value
            return elA;
        }
    }

    /**
     * Interface for a record that can be merged with itself.
     * <p>
     * Intended merge process is that, for each field:
     * <ol>
     * <li>If both of the two instances have a null value, then the result is null</li>
     * <li>If one of the two instances has a null value, then take the non-null value</li>
     * <li>If both are non-null, and the field is itself can be merged, then merge the values using the other merger</li>
     * <li>If both are non-null, and the field is a collection, then union the collections</li>
     * <li>Otherwise, keep the value in this instance (instead of the one in the other instance)</li>
     * </ol>
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
            comments = "Related class claim: mergerStaticClass"
    )
    interface Mergeable extends _MatchingInterface {
        /**
         * Merge the current instance into the other instance, if it is present
         * @return The result of the merge
         *
         * @param other The element to merge into this one, if it is present
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.iface.MergeOptionalMethod"},
                comments = "Related class claim: mergeInterfaceMergeOptionalMethod"
        )
        default MyRecordA merge(@NonNull final Optional<MyRecordA> other) {
            Objects.requireNonNull(other, "You cannot supply a null Optional parameter");
            return other.map(oth -> this.merge(oth)).orElse(this.merge((MyRecordA) null));
        }

        /**
         * Merge the current instance into the other instance.
         * @return The result of the merge
         *
         * @param other The element to merge into this one
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.iface.MergeMethod"},
                comments = "Related class claim: mergeInterfaceMergeMethod"
        )
        default MyRecordA merge(@Nullable final MyRecordA other) {
            final var optOther = Optional.ofNullable(other);
            return Builder.builder()
                    .someStringField(_MergerUtils.mergeString(this.someStringField(), optOther.map(MyRecordA::someStringField).orElse(null)))
                    .someIntField(_MergerUtils.mergeint(this.someIntField(), optOther.map(MyRecordA::someIntField).orElse(null)))
                    .build();
        }
    }

    /**
     * An interface that provides the ability to create new instances of a record with modifications
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WitherPrismInterfaceFactory"},
            comments = "Related class claim: wither"
    )
    interface With extends _MatchingInterface {
        /**
         * Creates a builder with the current fields
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BackToBuilder"},
                comments = "Related class claim: witherToBuilder"
        )
        default Builder with() {
            return Builder.builder()
                    .someStringField(this.someStringField())
                    .someIntField(this.someIntField());
        }

        /**
         * Allows creation of a copy of this instance with some tweaks via a builder
         *
         * @param subBuilder A function to modify a new copy of the object
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BuilderFluent"},
                comments = "Related class claim: witherFluentBuilder"
        )
        default MyRecordA with(@NonNull final Consumer<Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final Builder ___builder = this.with();
            subBuilder.accept(___builder);
            return ___builder.build();
        }

        /**
         * Return a new instance with a different {@code someIntField} field
         *
         * @param someIntField Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
                comments = "Related component claim: witherWith"
        )
        default MyRecordA withSomeIntField(final int someIntField) {
            return this.with()
                    .someIntField(someIntField)
                    .build();
        }

        /**
         * Return a new instance with a different {@code someStringField} field
         *
         * @param someStringField Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
                comments = "Related component claim: witherWith"
        )
        default MyRecordA withSomeStringField(final String someStringField) {
            return this.with()
                    .someStringField(someStringField)
                    .build();
        }
    }

    @NullUnmarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceFactory"},
            comments = "Related component claim: internalMatchingIface"
    )
    interface _MatchingInterface {
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
                comments = "Related component claim: internalMatchingIface"
        )
        int someIntField();

        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
                comments = "Related component claim: internalMatchingIface"
        )
        String someStringField();
    }
}
Generated MyRecordBUtils
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;

/**
 * An auto-generated utility class to work with {@link MyRecordB} objects
 * <p>
 * This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
 * <p>
 * For more details, see the GitHub page for cbarlin/advanced-record-utils
 */
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
        generatedFor = MyRecordB.class,
        version = @AdvancedRecordUtilsGenerated.Version(
                major = 0,
                minor = 6,
                patch = 0
        ),
        settings = @AdvancedRecordUtils(
                merger = true
        ),
        internalUtils = {
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "All", implementation = MyRecordBUtils.All.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "Builder", implementation = MyRecordBUtils.Builder.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "Mergeable", implementation = MyRecordBUtils.Mergeable.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "With", implementation = MyRecordBUtils.With.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "_MatchingInterface", implementation = MyRecordBUtils._MatchingInterface.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "_MergerUtils", implementation = MyRecordBUtils._MergerUtils.class)
        },
        references = {
            MyRecordAUtils.class
        },
        usedTypeConverters = {

        }
)
public final class MyRecordBUtils implements GeneratedUtil {
    /**
     * Create a blank builder of {@link MyRecordB}
     */
    @NonNull
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
            comments = "Related class claim: builderEmpty"
    )
    public static final Builder builder() {
        return Builder.builder();
    }

    /**
     * Creates a new builder of {@link MyRecordB} by copying an existing instance
     *
     * @param original The existing instance to copy
     */
    @NonNull
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
            comments = "Related class claim: builderCopy"
    )
    public static final Builder builder(final MyRecordB original) {
        return Builder.builder(original);
    }

    /**
     * A class used for building {@link MyRecordB} objects
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.BuilderClassCreatorVisitor"},
            comments = "Related class claim: builder"
    )
    public static final class Builder {
        @Nullable
        private MyRecordA otherItem;

        @Nullable
        private MyRecordB recursionFtw;

        @Nullable
        private MyRecordA woo;

        /**
         * Create a blank builder of {@link MyRecordB}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
                comments = "Related class claim: builderEmpty"
        )
        public static final Builder builder() {
            return new Builder();
        }

        /**
         * Creates a new builder of {@link MyRecordB} by copying an existing instance
         *
         * @param original The existing instance to copy
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
                comments = "Related class claim: builderCopy"
        )
        public static final Builder builder(final MyRecordB original) {
            Objects.requireNonNull(original, "Cannot copy a null instance");
            // "Copying an existing instance"
            return Builder.builder()
                    .otherItem(original.otherItem())
                    .woo(original.woo())
                    .recursionFtw(original.recursionFtw());
        }

        /**
         * Creates a new instance of {@link MyRecordB} from the fields set on this builder
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddPlainBuild"},
                comments = "Related class claim: builderBuild"
        )
        public MyRecordB build() {
            // "Creating new instance"
            return new MyRecordB(
                    this.otherItem(),
                    	this.woo(),
                    	this.recursionFtw()
                    );
        }

        /**
         * Returns the current value of {@code otherItem}
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
                comments = "Related component claim: builderGetter"
        )
        public MyRecordA otherItem() {
            return this.otherItem;
        }

        /**
         * Updates the value of {@code otherItem}
         * <p>
         * Supplying a null value will set the current value to null
         *
         * @param otherItem The replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
                comments = "Related component claim: builderPlainSetter"
        )
        public Builder otherItem(@Nullable final MyRecordA otherItem) {
            this.otherItem = otherItem;
            return this;
        }

        /**
         * Uses a supplied builder to replace the value at {@code otherItem}
         *
         * @param subBuilder Builder that can be used to replace {@code otherItem}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromRecord"},
                comments = "Related component claim: builderFluentSetter"
        )
        public Builder otherItem(@NonNull final Consumer<MyRecordAUtils.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final MyRecordAUtils.Builder builder = (Objects.isNull(this.otherItem())) ? MyRecordAUtils.Builder.builder() : MyRecordAUtils.Builder.builder(this.otherItem());
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.otherItem(builder.build());
        }

        /**
         * Returns the current value of {@code recursionFtw}
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
                comments = "Related component claim: builderGetter"
        )
        public MyRecordB recursionFtw() {
            return this.recursionFtw;
        }

        /**
         * Updates the value of {@code recursionFtw}
         * <p>
         * Supplying a null value will set the current value to null
         *
         * @param recursionFtw The replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
                comments = "Related component claim: builderPlainSetter"
        )
        public Builder recursionFtw(@Nullable final MyRecordB recursionFtw) {
            this.recursionFtw = recursionFtw;
            return this;
        }

        /**
         * Uses a supplied builder to replace the value at {@code recursionFtw}
         *
         * @param subBuilder Builder that can be used to replace {@code recursionFtw}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromRecord"},
                comments = "Related component claim: builderFluentSetter"
        )
        public Builder recursionFtw(@NonNull final Consumer<Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final Builder builder = (Objects.isNull(this.recursionFtw())) ? Builder.builder() : Builder.builder(this.recursionFtw());
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.recursionFtw(builder.build());
        }

        /**
         * Returns the current value of {@code woo}
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
                comments = "Related component claim: builderGetter"
        )
        public MyRecordA woo() {
            return this.woo;
        }

        /**
         * Updates the value of {@code woo}
         * <p>
         * Supplying a null value will set the current value to null
         *
         * @param woo The replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
                comments = "Related component claim: builderPlainSetter"
        )
        public Builder woo(@Nullable final MyRecordA woo) {
            this.woo = woo;
            return this;
        }

        /**
         * Uses a supplied builder to replace the value at {@code woo}
         *
         * @param subBuilder Builder that can be used to replace {@code woo}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromRecord"},
                comments = "Related component claim: builderFluentSetter"
        )
        public Builder woo(@NonNull final Consumer<MyRecordAUtils.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final MyRecordAUtils.Builder builder = (Objects.isNull(this.woo())) ? MyRecordAUtils.Builder.builder() : MyRecordAUtils.Builder.builder(this.woo());
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.woo(builder.build());
        }
    }

    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.AllInterfaceGenerator"},
            comments = "Related class claim: allIface"
    )
    public interface All extends Mergeable, With {
    }

    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
            comments = "Related class claim: mergerStaticClass"
    )
    public static final class _MergerUtils {
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
                comments = "Related class claim: mergerStaticClass"
        )
        private _MergerUtils() {
            throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
        }

        /**
         * Merge two instances of {@link MyRecordB} together
         *
         * @param preferred The preferred element
         * @param other The non-preferred element
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.MergeMethod"},
                comments = "Related class claim: mergeStaticMergeMethod"
        )
        public static MyRecordB merge(@Nullable final MyRecordB preferred, @Nullable final MyRecordB other) {
            if (Objects.isNull(other))  {
                // "Short-circuit of merge - other is null"
                return preferred;
            } else if (Objects.isNull(preferred)) {
                // "Short-circuit of merge - preferred is null"
                return other;
            }
            // "Merging two instances together"
            return Builder.builder()
                    .otherItem(_MergerUtils.mergeMyRecordA(preferred.otherItem(), other.otherItem()))
                    .woo(_MergerUtils.mergeMyRecordA(preferred.woo(), other.woo()))
                    .recursionFtw(_MergerUtils.mergeMyRecordB(preferred.recursionFtw(), other.recursionFtw()))
                    .build();
        }

        /**
         * Merger for fields of class {@link MyRecordA}
         *
         * @param elA The preferred input
         * @param elB The non-preferred input
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.OtherProcessed"},
                comments = "Related component claim: mergerAddFieldMergerMethod"
        )
        private static final MyRecordA mergeMyRecordA(@Nullable final MyRecordA elA, @Nullable final MyRecordA elB) {
            return MyRecordAUtils._MergerUtils.merge(elA, elB);
        }

        /**
         * Merger for fields of class {@link MyRecordB}
         *
         * @param elA The preferred input
         * @param elB The non-preferred input
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.OtherProcessed"},
                comments = "Related component claim: mergerAddFieldMergerMethod"
        )
        private static final MyRecordB mergeMyRecordB(@Nullable final MyRecordB elA, @Nullable final MyRecordB elB) {
            return _MergerUtils.merge(elA, elB);
        }
    }

    /**
     * Interface for a record that can be merged with itself.
     * <p>
     * Intended merge process is that, for each field:
     * <ol>
     * <li>If both of the two instances have a null value, then the result is null</li>
     * <li>If one of the two instances has a null value, then take the non-null value</li>
     * <li>If both are non-null, and the field is itself can be merged, then merge the values using the other merger</li>
     * <li>If both are non-null, and the field is a collection, then union the collections</li>
     * <li>Otherwise, keep the value in this instance (instead of the one in the other instance)</li>
     * </ol>
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
            comments = "Related class claim: mergerStaticClass"
    )
    interface Mergeable extends _MatchingInterface {
        /**
         * Merge the current instance into the other instance, if it is present
         * @return The result of the merge
         *
         * @param other The element to merge into this one, if it is present
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.iface.MergeOptionalMethod"},
                comments = "Related class claim: mergeInterfaceMergeOptionalMethod"
        )
        default MyRecordB merge(@NonNull final Optional<MyRecordB> other) {
            Objects.requireNonNull(other, "You cannot supply a null Optional parameter");
            return other.map(oth -> this.merge(oth)).orElse(this.merge((MyRecordB) null));
        }

        /**
         * Merge the current instance into the other instance.
         * @return The result of the merge
         *
         * @param other The element to merge into this one
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.iface.MergeMethod"},
                comments = "Related class claim: mergeInterfaceMergeMethod"
        )
        default MyRecordB merge(@Nullable final MyRecordB other) {
            final var optOther = Optional.ofNullable(other);
            return Builder.builder()
                    .otherItem(_MergerUtils.mergeMyRecordA(this.otherItem(), optOther.map(MyRecordB::otherItem).orElse(null)))
                    .woo(_MergerUtils.mergeMyRecordA(this.woo(), optOther.map(MyRecordB::woo).orElse(null)))
                    .recursionFtw(_MergerUtils.mergeMyRecordB(this.recursionFtw(), optOther.map(MyRecordB::recursionFtw).orElse(null)))
                    .build();
        }
    }

    /**
     * An interface that provides the ability to create new instances of a record with modifications
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WitherPrismInterfaceFactory"},
            comments = "Related class claim: wither"
    )
    interface With extends _MatchingInterface {
        /**
         * Creates a builder with the current fields
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BackToBuilder"},
                comments = "Related class claim: witherToBuilder"
        )
        default Builder with() {
            return Builder.builder()
                    .otherItem(this.otherItem())
                    .woo(this.woo())
                    .recursionFtw(this.recursionFtw());
        }

        /**
         * Allows creation of a copy of this instance with some tweaks via a builder
         *
         * @param subBuilder A function to modify a new copy of the object
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BuilderFluent"},
                comments = "Related class claim: witherFluentBuilder"
        )
        default MyRecordB with(@NonNull final Consumer<Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final Builder ___builder = this.with();
            subBuilder.accept(___builder);
            return ___builder.build();
        }

        /**
         * Return a new instance with a different {@code otherItem} field
         *
         * @param otherItem Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
                comments = "Related component claim: witherWith"
        )
        default MyRecordB withOtherItem(final MyRecordA otherItem) {
            return this.with()
                    .otherItem(otherItem)
                    .build();
        }

        /**
         * Return a new instance with a different {@code otherItem} field, obtaining the value by invoking the builder
         *
         * @param subBuilder Builder that can be used to replace {@code otherItem}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithFluentMethod"},
                comments = "Related component claim: witherWithFluent"
        )
        default MyRecordB withOtherItem(@NonNull final Consumer<MyRecordAUtils.Builder> subBuilder) {
            return this.with()
                    .otherItem(subBuilder)
                    .build();
        }

        /**
         * Return a new instance with a different {@code recursionFtw} field
         *
         * @param recursionFtw Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
                comments = "Related component claim: witherWith"
        )
        default MyRecordB withRecursionFtw(final MyRecordB recursionFtw) {
            return this.with()
                    .recursionFtw(recursionFtw)
                    .build();
        }

        /**
         * Return a new instance with a different {@code recursionFtw} field, obtaining the value by invoking the builder
         *
         * @param subBuilder Builder that can be used to replace {@code recursionFtw}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithFluentMethod"},
                comments = "Related component claim: witherWithFluent"
        )
        default MyRecordB withRecursionFtw(@NonNull final Consumer<Builder> subBuilder) {
            return this.with()
                    .recursionFtw(subBuilder)
                    .build();
        }

        /**
         * Return a new instance with a different {@code woo} field
         *
         * @param woo Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
                comments = "Related component claim: witherWith"
        )
        default MyRecordB withWoo(final MyRecordA woo) {
            return this.with()
                    .woo(woo)
                    .build();
        }

        /**
         * Return a new instance with a different {@code woo} field, obtaining the value by invoking the builder
         *
         * @param subBuilder Builder that can be used to replace {@code woo}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithFluentMethod"},
                comments = "Related component claim: witherWithFluent"
        )
        default MyRecordB withWoo(@NonNull final Consumer<MyRecordAUtils.Builder> subBuilder) {
            return this.with()
                    .woo(subBuilder)
                    .build();
        }
    }

    @NullUnmarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceFactory"},
            comments = "Related component claim: internalMatchingIface"
    )
    interface _MatchingInterface {
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
                comments = "Related component claim: internalMatchingIface"
        )
        MyRecordA otherItem();

        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
                comments = "Related component claim: internalMatchingIface"
        )
        MyRecordB recursionFtw();

        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
                comments = "Related component claim: internalMatchingIface"
        )
        MyRecordA woo();
    }
}

And you can use them both:

Sample method body
final MyRecordA myA = MyRecordAUtils.builder().someStringField("blah").someIntField(42).build();
final MyRecordB firstRound = MyRecordBUtils.builder()
    .woo(myA)
    // A fluent use of a sub-builder is available by default
    .otherItem((MyRecordAUtils.Builder otherBuilder) -> otherBuilder.someIntField(42))
    .build();
final MyRecordB secondRound = MyRecordBUtils.builder(firstRound)
    // You aren't required to define the type either
    .otherItem(otherBuilder -> otherBuilder.someIntField(420))
    .build();

Since MyRecordB had enabled merger then MyRecordA will also have a merger, so MyRecordA instances can be merged too!

Deep or partially adopted hierarchies can introduce edge cases. If only some records in a chain implement the generated interfaces (e.g., via per-record adoption, cross-package imports, or mixed compilation units), certain deep behaviours (like deep diffs or mergers) may stop at non-participating boundaries. Prefer annotating the root(s) so the cascade can generate utilities across the entire in-package tree.

3.4. Package usage

It is also possible to annotate a package. This has two possible uses:

  • setting some defaults for all records in that package

  • (optionally) automatically applying to all items in that package

Packages will not have their own *Utils classes generated

3.4.1. Using a package to set default settings

By placing an annotation on a package element and adding some settings, those settings are applied to all annotated records in that package.

Say we have the following:

package-info.java
@io.github.cbarlin.aru.annotations.AdvancedRecordUtils(merger = true)
package org.example;
Person.java
package org.example;
@AdvancedRecordUtils
public record Person(String name, int age, List<String> favouriteColours) {}
Address.java
package org.example;
@AdvancedRecordUtils
public record Address(String country, String state) {}

Then that would be the equivalent of doing the following and not having a package-info.java:

Person.java
package org.example;
@AdvancedRecordUtils(merger = true)
public record Person(String name, int age, List<String> favouriteColours) {}
Address.java
package org.example;
@AdvancedRecordUtils(merger = true)
public record Address(String country, String state) {}

If an annotation inside a package has configuration items, then the processor will merge the settings, giving priority to items that are on the record. As an example:

package-info.java
@AdvancedRecordUtils(
    builderOptions = @AdvancedRecordUtils.BuilderOptions(
        buildMethodName = "make",
        setTimeNowMethodPrefix = "update"
    )
)
package org.example;

import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
Person.java
package org.example;
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        builtCollectionType = BuiltCollectionType.JAVA_IMMUTABLE,
        setTimeNowMethodPrefix = "ensure"
    )
)
public record Person(String name, int age, List<String> favouriteColours) {}

Then that would be the same as doing the following and not having a package-info.java:

Person.java
package org.example;
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        buildMethodName = "make",
        builtCollectionType = BuiltCollectionType.JAVA_IMMUTABLE,
        // This was set on both the package and record - record took priority
        setTimeNowMethodPrefix = "ensure"
    )
)
public record Person(String name, int age, List<String> favouriteColours) {}

3.4.2. Automatically applying to all items in a package

You can also request that all records within a given package have *Utils classes generated for them. These records will use the settings specified on the package, similar to the above case of packages specifying default settings. To use, set the applyToAllInPackage to true. You can still add annotations on the individual records including settings for that record, and that record will respect its own settings.

Say we have the following:

package-info.java
@io.github.cbarlin.aru.annotations.AdvancedRecordUtils(merger = true, applyToAllInPackage = true)
package org.example;
Person.java
package org.example;
public record Person(String name, int age, List<String> favouriteColours) {}
Address.java
package org.example;
public record Address(String country, String state) {}

Then that would be the equivalent of doing the following and not having a package-info.java:

Person.java
package org.example;
@AdvancedRecordUtils(merger = true)
public record Person(String name, int age, List<String> favouriteColours) {}
Address.java
package org.example;
@AdvancedRecordUtils(merger = true)
public record Address(String country, String state) {}

3.5. Meta-annotations

You can also use meta-annotations to define a group of settings to apply to a number of disparate records/interfaces. Like the normal annotation, these settings cascade (see above for details), and like the normal annotation the settings it defines get "merged" with others. The order of preference for the merge is:

  1. Settings on the record itself

  2. Settings inherited via meta-annotations

  3. Settings inherited from the package

  4. Settings inherited from the meta-annotation’s package

Meta-annotations only apply within one compilation unit (e.g. Maven module). They do not work across compilation boundaries.

With the following example:

package-info.java
@AdvancedRecordUtils(
    merger = true,
    applyToAllInPackage = true,
    builderOptions = @AdvancedRecordUtils.BuilderOptions(
        buildMethodName = "make"
    )
)
package org.example;

import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
GenerateDiff.java
@io.github.cbarlin.aru.annotations.AdvancedRecordUtils(
    diffable = true,
    builderOptions = @AdvancedRecordUtils.BuilderOptions(
        buildMethodName = "be"
    )
)
public @interface GenerateDiff {}
Person.java
package org.example;
public record Person(String name, int age, List<String> favouriteColours) {}
Address.java
package org.example;
@GenerateDiff
@AdvancedRecordUtils(
    builderOptions = @AdvancedRecordUtils.BuilderOptions(
        buildMethodName = "make"
    )
)
public record Address(String country, String state) {}
Video.java
package org.example;
@GenerateDiff
public record Video(String name, int uploadYear) {}

Then that is the equivalent of doing the following without the package-info.java or the GenerateDiff annotation:

Person.java
@AdvancedRecordUtils(
    merger = true,
    builderOptions = @AdvancedRecordUtils.BuilderOptions(
        buildMethodName = "make"
    )
)
public record Person(String name, int age, List<String> favouriteColours) {}
Address.java
@AdvancedRecordUtils(
    merger = true,
    diffable = true,
    builderOptions = @AdvancedRecordUtils.BuilderOptions(
        buildMethodName = "make"
    )
)
public record Address(String country, String state) {}
Video.java
@AdvancedRecordUtils(
    merger = true,
    diffable = true,
    builderOptions = @AdvancedRecordUtils.BuilderOptions(
        buildMethodName = "be"
    )
)
public record Video(String name, int uploadYear) {}
There is an @AdvancedRecordUtilsFull which acts similarly to this (except that it is known to the processor ahead-of-time), and has most optional settings turned on.

3.6. Interfaces

Advanced Record Utils can handle the use of interfaces in a tree of records that use/reference each other.

Take the following structure:

This is to give you a baseline for the following content - doing exactly this will not work as you expect!
SomeInterface.java
public interface SomeInterface {
}
MyRecordFOptionA.java
public record MyRecordFOptionA(
    String someStrField
) implements SomeInterface {
}
MyRecordFOptionB.java
public record MyRecordFOptionB(
    int someIntField
) implements SomeInterface {
}
MyRecordG.java
@AdvancedRecordUtils
public record MyRecordG(
    SomeInterface anInterface
) {}

As described above, the processor is not able to work with the SomeInterface interface, and so will not generate *Utils classes for the two implementing records, nor will there be builder methods (such as fluent setters) for them. This also applies to any other utilities generated - no mergers or differs - and so those tools will fall back to default methods of working.

There are several approaches that can be used to resolve this.

3.6.1. Seal the interface

If you change the definition of the interface so that it is sealed, like so:

SomeInterface.java
public sealed interface SomeInterface
permits MyRecordFOptionA, MyRecordFOptionB {
}
MyRecordFOptionA.java
public record MyRecordFOptionA(
    String someStrField
) implements SomeInterface {
}
MyRecordFOptionB.java
public record MyRecordFOptionB(
    int someIntField
) implements SomeInterface {
}
MyRecordG.java
@AdvancedRecordUtils
public record MyRecordG(
    SomeInterface anInterface
) {}

Then the processor will be able to read the permits clause and the cascading ability will build *Utils classes for:

  • SomeInterface

  • MyRecordFOptionA

  • MyRecordFOptionB

Generated *Utils classes for the interface
SomeInterfaceUtils
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import org.jspecify.annotations.NullMarked;

/**
 * The Utils class for an interface. Serves mostly to point to concrete implementationsAn auto-generated utility class to work with {@link SomeInterface} objects
 * <p>
 * This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
 * <p>
 * For more details, see the GitHub page for cbarlin/advanced-record-utils
 */
@NullMarked
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
        generatedFor = SomeInterface.class,
        version = @AdvancedRecordUtilsGenerated.Version(
                major = 0,
                minor = 6,
                patch = 0
        ),
        settings = @AdvancedRecordUtils(
                builderOptions = @AdvancedRecordUtils.BuilderOptions(copyCreationName = "from", builtCollectionType = AdvancedRecordUtils.BuiltCollectionType.AUTO),
                attemptToFindExistingUtils = true
        ),
        internalUtils = {

        },
        references = {
            MyRecordFOptionAUtils.class,
            MyRecordFOptionBUtils.class
        },
        usedTypeConverters = {

        }
)
public final class SomeInterfaceUtils implements GeneratedUtil {
    /**
     * Obtain the builder for {@link MyRecordFOptionA}
     */
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderEmpty"},
            comments = "Related class claim: builderEmpty"
    )
    public static final MyRecordFOptionAUtils.Builder builderAsMyRecordFOptionA() {
        return MyRecordFOptionAUtils.Builder.builder();
    }

    /**
     * Obtain the builder for {@link MyRecordFOptionB}
     */
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderEmpty"},
            comments = "Related class claim: builderEmpty"
    )
    public static final MyRecordFOptionBUtils.Builder builderAsMyRecordFOptionB() {
        return MyRecordFOptionBUtils.Builder.builder();
    }

    /**
     * Obtain the builder for {@link MyRecordFOptionA}
     *
     * @param existing The instance to copy
     */
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderCopy"},
            comments = "Related class claim: builderCopy"
    )
    public static final MyRecordFOptionAUtils.Builder fromAsMyRecordFOptionA(final MyRecordFOptionA existing) {
        return MyRecordFOptionAUtils.Builder.from(existing);
    }

    /**
     * Obtain the builder for {@link MyRecordFOptionB}
     *
     * @param existing The instance to copy
     */
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderCopy"},
            comments = "Related class claim: builderCopy"
    )
    public static final MyRecordFOptionBUtils.Builder fromAsMyRecordFOptionB(final MyRecordFOptionB existing) {
        return MyRecordFOptionBUtils.Builder.from(existing);
    }
}
MyRecordGUtils
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.Consumer;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;

/**
 * An auto-generated utility class to work with {@link MyRecordG} objects
 * <p>
 * This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
 * <p>
 * For more details, see the GitHub page for cbarlin/advanced-record-utils
 */
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
        generatedFor = MyRecordG.class,
        version = @AdvancedRecordUtilsGenerated.Version(
                major = 0,
                minor = 6,
                patch = 0
        ),
        settings = @AdvancedRecordUtils(
                builderOptions = @AdvancedRecordUtils.BuilderOptions(copyCreationName = "from", builtCollectionType = AdvancedRecordUtils.BuiltCollectionType.AUTO),
                attemptToFindExistingUtils = true
        ),
        internalUtils = {
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "All", implementation = MyRecordGUtils.All.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "Builder", implementation = MyRecordGUtils.Builder.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "With", implementation = MyRecordGUtils.With.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "_MatchingInterface", implementation = MyRecordGUtils._MatchingInterface.class)
        },
        references = {
            MyRecordFOptionAUtils.class,
            MyRecordFOptionBUtils.class,
            SomeInterfaceUtils.class
        },
        usedTypeConverters = {

        }
)
public final class MyRecordGUtils implements GeneratedUtil {
    /**
     * Create a blank builder of {@link MyRecordG}
     */
    @NonNull
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
            comments = "Related class claim: builderEmpty"
    )
    public static final Builder builder() {
        return Builder.builder();
    }

    /**
     * Creates a new builder of {@link MyRecordG} by copying an existing instance
     *
     * @param original The existing instance to copy
     */
    @NonNull
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
            comments = "Related class claim: builderCopy"
    )
    public static final Builder from(final MyRecordG original) {
        return Builder.from(original);
    }

    /**
     * A class used for building {@link MyRecordG} objects
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.BuilderClassCreatorVisitor"},
            comments = "Related class claim: builder"
    )
    public static final class Builder {
        @Nullable
        private SomeInterface anInterface;

        @NonNull
        private ArrayList<SomeInterface> listOfInterface = new ArrayList<SomeInterface>();

        /**
         * Create a blank builder of {@link MyRecordG}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
                comments = "Related class claim: builderEmpty"
        )
        public static final Builder builder() {
            return new Builder();
        }

        /**
         * Creates a new builder of {@link MyRecordG} by copying an existing instance
         *
         * @param original The existing instance to copy
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
                comments = "Related class claim: builderCopy"
        )
        public static final Builder from(final MyRecordG original) {
            Objects.requireNonNull(original, "Cannot copy a null instance");
            // "Copying an existing instance"
            return Builder.builder()
                    .anInterface(original.anInterface())
                    .listOfInterface(original.listOfInterface());
        }

        /**
         * Add a singular {@link SomeInterface} to the collection for the field {@code listOfInterface}
         * <p>
         * Supplying a null value will set the current value to null
         *
         * @param listOfInterface A singular instance to be added to the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAdder"},
                comments = "Related component claim: builderAdd"
        )
        public Builder addListOfInterface(@Nullable final SomeInterface listOfInterface) {
            this.listOfInterface.add(listOfInterface);
            return this;
        }

        /**
         * Adds all elements of the provided collection to {@code listOfInterface}
         *
         * @param listOfInterface A collection to be merged into the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
                comments = "Related component claim: builderAddAllIterable"
        )
        public Builder addListOfInterface(@NonNull final Collection<SomeInterface> listOfInterface) {
            if (Objects.nonNull(listOfInterface)) {
                this.listOfInterface.addAll(listOfInterface);
            }
            return this;
        }

        /**
         * Adds all elements of the provided iterable to {@code listOfInterface}
         *
         * @param listOfInterface An iterable to be merged into the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
                comments = "Related component claim: builderAddAllIterable"
        )
        public Builder addListOfInterface(@NonNull final Iterable<SomeInterface> listOfInterface) {
            if (Objects.nonNull(listOfInterface)) {
                for (final SomeInterface __addable : listOfInterface) {
                    this.addListOfInterface(__addable);
                }
            }
            return this;
        }

        /**
         * Adds all elements of the provided iterator to {@code listOfInterface}
         *
         * @param listOfInterface An iterator to be merged into the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
                comments = "Related component claim: builderAddAllIterable"
        )
        public Builder addListOfInterface(@NonNull final Iterator<SomeInterface> listOfInterface) {
            if (Objects.nonNull(listOfInterface)) {
                while(listOfInterface.hasNext()) {
                    this.addListOfInterface(listOfInterface.next());
                }
            }
            return this;
        }

        /**
         * Adds all elements of the provided spliterator to {@code listOfInterface}
         *
         * @param listOfInterface A spliterator to be merged into the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
                comments = "Related component claim: builderAddAllIterable"
        )
        public Builder addListOfInterface(@NonNull final Spliterator<SomeInterface> listOfInterface) {
            if (Objects.nonNull(listOfInterface)) {
                listOfInterface.forEachRemaining(this::addListOfInterface);
            }
            return this;
        }

        /**
         * Uses a supplied builder to build an instance of {@link MyRecordFOptionA} and add to the value of {@link addListOfInterface}
         *
         * @param subBuilder Builder used to invoke {@code addListOfInterface}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddFluentAdderFromInterface"},
                comments = "Related component claim: builderFluentSetter"
        )
        public Builder addListOfInterfaceToMyRecordFOptionA(@NonNull final Consumer<MyRecordFOptionAUtils.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final MyRecordFOptionAUtils.Builder builder = MyRecordFOptionAUtils.Builder.builder();
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.addListOfInterface(builder.build());
        }

        /**
         * Uses a supplied builder to build an instance of {@link MyRecordFOptionB} and add to the value of {@link addListOfInterface}
         *
         * @param subBuilder Builder used to invoke {@code addListOfInterface}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddFluentAdderFromInterface"},
                comments = "Related component claim: builderFluentSetter"
        )
        public Builder addListOfInterfaceToMyRecordFOptionB(@NonNull final Consumer<MyRecordFOptionBUtils.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final MyRecordFOptionBUtils.Builder builder = MyRecordFOptionBUtils.Builder.builder();
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.addListOfInterface(builder.build());
        }

        /**
         * Returns the current value of {@code anInterface}
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
                comments = "Related component claim: builderGetter"
        )
        public SomeInterface anInterface() {
            return this.anInterface;
        }

        /**
         * Updates the value of {@code anInterface}
         * <p>
         * Supplying a null value will set the current value to null
         *
         * @param anInterface The replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
                comments = "Related component claim: builderPlainSetter"
        )
        public Builder anInterface(@Nullable final SomeInterface anInterface) {
            this.anInterface = anInterface;
            return this;
        }

        /**
         * Uses a supplied builder to build an instance of {@link MyRecordFOptionA} and replace the value of {@link anInterface}
         *
         * @param subBuilder Builder that can be used to replace {@code anInterface}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromInterface"},
                comments = "Related component claim: builderFluentSetter"
        )
        public Builder anInterfaceAsMyRecordFOptionA(@NonNull final Consumer<MyRecordFOptionAUtils.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final MyRecordFOptionAUtils.Builder builder;
            if (Objects.nonNull(this.anInterface()) && this.anInterface() instanceof MyRecordFOptionA oth) {
                builder = MyRecordFOptionAUtils.Builder.from(oth);
            } else {
                builder = MyRecordFOptionAUtils.Builder.builder();
            }
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.anInterface(builder.build());
        }

        /**
         * Uses a supplied builder to build an instance of {@link MyRecordFOptionB} and replace the value of {@link anInterface}
         *
         * @param subBuilder Builder that can be used to replace {@code anInterface}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromInterface"},
                comments = "Related component claim: builderFluentSetter"
        )
        public Builder anInterfaceAsMyRecordFOptionB(@NonNull final Consumer<MyRecordFOptionBUtils.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final MyRecordFOptionBUtils.Builder builder;
            if (Objects.nonNull(this.anInterface()) && this.anInterface() instanceof MyRecordFOptionB oth) {
                builder = MyRecordFOptionBUtils.Builder.from(oth);
            } else {
                builder = MyRecordFOptionBUtils.Builder.builder();
            }
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.anInterface(builder.build());
        }

        /**
         * Creates a new instance of {@link MyRecordG} from the fields set on this builder
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddPlainBuild"},
                comments = "Related class claim: builderBuild"
        )
        public MyRecordG build() {
            // "Creating new instance"
            return new MyRecordG(
                    this.anInterface(),
                    	this.listOfInterface()
                    );
        }

        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddGetter"},
                comments = "Related component claim: builderGetter"
        )
        public List<SomeInterface> listOfInterface() {
            return this.listOfInterface;
        }

        /**
         * Updates the value of {@code listOfInterface}
         * <p>
         * Supplying a null value will set the current value to null/empty
         *
         * @param listOfInterface The replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddSetter"},
                comments = "Related component claim: builderPlainSetter"
        )
        public Builder listOfInterface(@Nullable final List<SomeInterface> listOfInterface) {
            this.listOfInterface.clear();
            if (Objects.nonNull(listOfInterface)) {
                this.listOfInterface.addAll(listOfInterface);
            }
            return this;
        }
    }

    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.AllInterfaceGenerator"},
            comments = "Related class claim: allIface"
    )
    public interface All extends With {
    }

    /**
     * An interface that provides the ability to create new instances of a record with modifications
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WitherPrismInterfaceFactory"},
            comments = "Related class claim: wither"
    )
    interface With extends _MatchingInterface {
        /**
         * Creates a builder with the current fields
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BackToBuilder"},
                comments = "Related class claim: witherToBuilder"
        )
        default Builder with() {
            return Builder.builder()
                    .anInterface(this.anInterface())
                    .listOfInterface(this.listOfInterface());
        }

        /**
         * Allows creation of a copy of this instance with some tweaks via a builder
         *
         * @param subBuilder A function to modify a new copy of the object
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BuilderFluent"},
                comments = "Related class claim: witherFluentBuilder"
        )
        default MyRecordG with(@NonNull final Consumer<Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final Builder ___builder = this.with();
            subBuilder.accept(___builder);
            return ___builder.build();
        }

        /**
         * Return a new instance with a different {@code listOfInterface} field
         *
         * @param listOfInterface Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithAdd"},
                comments = "Related component claim: witherWithAdd"
        )
        default MyRecordG withAddListOfInterface(final List<SomeInterface> listOfInterface) {
            return this.with()
                    .addListOfInterface(listOfInterface)
                    .build();
        }

        /**
         * Return a new instance with a different {@code anInterface} field
         *
         * @param anInterface Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
                comments = "Related component claim: witherWith"
        )
        default MyRecordG withAnInterface(final SomeInterface anInterface) {
            return this.with()
                    .anInterface(anInterface)
                    .build();
        }

        /**
         * Return a new instance with a different {@code listOfInterface} field
         *
         * @param listOfInterface Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
                comments = "Related component claim: witherWith"
        )
        default MyRecordG withListOfInterface(final List<SomeInterface> listOfInterface) {
            return this.with()
                    .listOfInterface(listOfInterface)
                    .build();
        }
    }

    @NullUnmarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceFactory"},
            comments = "Related component claim: internalMatchingIface"
    )
    interface _MatchingInterface {
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
                comments = "Related component claim: internalMatchingIface"
        )
        SomeInterface anInterface();

        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
                comments = "Related component claim: internalMatchingIface"
        )
        List<SomeInterface> listOfInterface();
    }
}

3.6.2. Annotate the records

If you annotate the records that implement the interface as well as MyRecordG like so:

SomeInterface.java
public interface SomeInterface {
}
MyRecordFOptionA.java
@AdvancedRecordUtils
public record MyRecordFOptionA(
    String someStrField
) implements SomeInterface {
}
MyRecordFOptionB.java
@AdvancedRecordUtils
public record MyRecordFOptionB(
    int someIntField
) implements SomeInterface {
}
MyRecordG.java
@AdvancedRecordUtils
public record MyRecordG(
    SomeInterface anInterface
) {}

Then when the processor reads the interface defined on both of the records it is able to incorporate it into its internal workflows. This means it will build *Utils classes for:

  • SomeInterface

  • MyRecordFOptionA

  • MyRecordFOptionB

The below collapsible is the same as the one under "Seal the interface"
Generated *Utils classes for the interface
SomeInterfaceUtils
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import org.jspecify.annotations.NullMarked;

/**
 * The Utils class for an interface. Serves mostly to point to concrete implementationsAn auto-generated utility class to work with {@link SomeInterface} objects
 * <p>
 * This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
 * <p>
 * For more details, see the GitHub page for cbarlin/advanced-record-utils
 */
@NullMarked
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
        generatedFor = SomeInterface.class,
        version = @AdvancedRecordUtilsGenerated.Version(
                major = 0,
                minor = 6,
                patch = 0
        ),
        settings = @AdvancedRecordUtils(
                builderOptions = @AdvancedRecordUtils.BuilderOptions(copyCreationName = "from", builtCollectionType = AdvancedRecordUtils.BuiltCollectionType.AUTO),
                attemptToFindExistingUtils = true
        ),
        internalUtils = {

        },
        references = {
            MyRecordFOptionAUtils.class,
            MyRecordFOptionBUtils.class
        },
        usedTypeConverters = {

        }
)
public final class SomeInterfaceUtils implements GeneratedUtil {
    /**
     * Obtain the builder for {@link MyRecordFOptionA}
     */
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderEmpty"},
            comments = "Related class claim: builderEmpty"
    )
    public static final MyRecordFOptionAUtils.Builder builderAsMyRecordFOptionA() {
        return MyRecordFOptionAUtils.Builder.builder();
    }

    /**
     * Obtain the builder for {@link MyRecordFOptionB}
     */
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderEmpty"},
            comments = "Related class claim: builderEmpty"
    )
    public static final MyRecordFOptionBUtils.Builder builderAsMyRecordFOptionB() {
        return MyRecordFOptionBUtils.Builder.builder();
    }

    /**
     * Obtain the builder for {@link MyRecordFOptionA}
     *
     * @param existing The instance to copy
     */
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderCopy"},
            comments = "Related class claim: builderCopy"
    )
    public static final MyRecordFOptionAUtils.Builder fromAsMyRecordFOptionA(final MyRecordFOptionA existing) {
        return MyRecordFOptionAUtils.Builder.from(existing);
    }

    /**
     * Obtain the builder for {@link MyRecordFOptionB}
     *
     * @param existing The instance to copy
     */
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderCopy"},
            comments = "Related class claim: builderCopy"
    )
    public static final MyRecordFOptionBUtils.Builder fromAsMyRecordFOptionB(final MyRecordFOptionB existing) {
        return MyRecordFOptionBUtils.Builder.from(existing);
    }
}
MyRecordGUtils
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.Consumer;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;

/**
 * An auto-generated utility class to work with {@link MyRecordG} objects
 * <p>
 * This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
 * <p>
 * For more details, see the GitHub page for cbarlin/advanced-record-utils
 */
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
        generatedFor = MyRecordG.class,
        version = @AdvancedRecordUtilsGenerated.Version(
                major = 0,
                minor = 6,
                patch = 0
        ),
        settings = @AdvancedRecordUtils(
                builderOptions = @AdvancedRecordUtils.BuilderOptions(copyCreationName = "from", builtCollectionType = AdvancedRecordUtils.BuiltCollectionType.AUTO),
                attemptToFindExistingUtils = true
        ),
        internalUtils = {
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "All", implementation = MyRecordGUtils.All.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "Builder", implementation = MyRecordGUtils.Builder.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "With", implementation = MyRecordGUtils.With.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "_MatchingInterface", implementation = MyRecordGUtils._MatchingInterface.class)
        },
        references = {
            MyRecordFOptionAUtils.class,
            MyRecordFOptionBUtils.class,
            SomeInterfaceUtils.class
        },
        usedTypeConverters = {

        }
)
public final class MyRecordGUtils implements GeneratedUtil {
    /**
     * Create a blank builder of {@link MyRecordG}
     */
    @NonNull
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
            comments = "Related class claim: builderEmpty"
    )
    public static final Builder builder() {
        return Builder.builder();
    }

    /**
     * Creates a new builder of {@link MyRecordG} by copying an existing instance
     *
     * @param original The existing instance to copy
     */
    @NonNull
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
            comments = "Related class claim: builderCopy"
    )
    public static final Builder from(final MyRecordG original) {
        return Builder.from(original);
    }

    /**
     * A class used for building {@link MyRecordG} objects
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.BuilderClassCreatorVisitor"},
            comments = "Related class claim: builder"
    )
    public static final class Builder {
        @Nullable
        private SomeInterface anInterface;

        @NonNull
        private ArrayList<SomeInterface> listOfInterface = new ArrayList<SomeInterface>();

        /**
         * Create a blank builder of {@link MyRecordG}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
                comments = "Related class claim: builderEmpty"
        )
        public static final Builder builder() {
            return new Builder();
        }

        /**
         * Creates a new builder of {@link MyRecordG} by copying an existing instance
         *
         * @param original The existing instance to copy
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
                comments = "Related class claim: builderCopy"
        )
        public static final Builder from(final MyRecordG original) {
            Objects.requireNonNull(original, "Cannot copy a null instance");
            // "Copying an existing instance"
            return Builder.builder()
                    .anInterface(original.anInterface())
                    .listOfInterface(original.listOfInterface());
        }

        /**
         * Add a singular {@link SomeInterface} to the collection for the field {@code listOfInterface}
         * <p>
         * Supplying a null value will set the current value to null
         *
         * @param listOfInterface A singular instance to be added to the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAdder"},
                comments = "Related component claim: builderAdd"
        )
        public Builder addListOfInterface(@Nullable final SomeInterface listOfInterface) {
            this.listOfInterface.add(listOfInterface);
            return this;
        }

        /**
         * Adds all elements of the provided collection to {@code listOfInterface}
         *
         * @param listOfInterface A collection to be merged into the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
                comments = "Related component claim: builderAddAllIterable"
        )
        public Builder addListOfInterface(@NonNull final Collection<SomeInterface> listOfInterface) {
            if (Objects.nonNull(listOfInterface)) {
                this.listOfInterface.addAll(listOfInterface);
            }
            return this;
        }

        /**
         * Adds all elements of the provided iterable to {@code listOfInterface}
         *
         * @param listOfInterface An iterable to be merged into the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
                comments = "Related component claim: builderAddAllIterable"
        )
        public Builder addListOfInterface(@NonNull final Iterable<SomeInterface> listOfInterface) {
            if (Objects.nonNull(listOfInterface)) {
                for (final SomeInterface __addable : listOfInterface) {
                    this.addListOfInterface(__addable);
                }
            }
            return this;
        }

        /**
         * Adds all elements of the provided iterator to {@code listOfInterface}
         *
         * @param listOfInterface An iterator to be merged into the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
                comments = "Related component claim: builderAddAllIterable"
        )
        public Builder addListOfInterface(@NonNull final Iterator<SomeInterface> listOfInterface) {
            if (Objects.nonNull(listOfInterface)) {
                while(listOfInterface.hasNext()) {
                    this.addListOfInterface(listOfInterface.next());
                }
            }
            return this;
        }

        /**
         * Adds all elements of the provided spliterator to {@code listOfInterface}
         *
         * @param listOfInterface A spliterator to be merged into the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
                comments = "Related component claim: builderAddAllIterable"
        )
        public Builder addListOfInterface(@NonNull final Spliterator<SomeInterface> listOfInterface) {
            if (Objects.nonNull(listOfInterface)) {
                listOfInterface.forEachRemaining(this::addListOfInterface);
            }
            return this;
        }

        /**
         * Uses a supplied builder to build an instance of {@link MyRecordFOptionA} and add to the value of {@link addListOfInterface}
         *
         * @param subBuilder Builder used to invoke {@code addListOfInterface}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddFluentAdderFromInterface"},
                comments = "Related component claim: builderFluentSetter"
        )
        public Builder addListOfInterfaceToMyRecordFOptionA(@NonNull final Consumer<MyRecordFOptionAUtils.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final MyRecordFOptionAUtils.Builder builder = MyRecordFOptionAUtils.Builder.builder();
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.addListOfInterface(builder.build());
        }

        /**
         * Uses a supplied builder to build an instance of {@link MyRecordFOptionB} and add to the value of {@link addListOfInterface}
         *
         * @param subBuilder Builder used to invoke {@code addListOfInterface}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddFluentAdderFromInterface"},
                comments = "Related component claim: builderFluentSetter"
        )
        public Builder addListOfInterfaceToMyRecordFOptionB(@NonNull final Consumer<MyRecordFOptionBUtils.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final MyRecordFOptionBUtils.Builder builder = MyRecordFOptionBUtils.Builder.builder();
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.addListOfInterface(builder.build());
        }

        /**
         * Returns the current value of {@code anInterface}
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
                comments = "Related component claim: builderGetter"
        )
        public SomeInterface anInterface() {
            return this.anInterface;
        }

        /**
         * Updates the value of {@code anInterface}
         * <p>
         * Supplying a null value will set the current value to null
         *
         * @param anInterface The replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
                comments = "Related component claim: builderPlainSetter"
        )
        public Builder anInterface(@Nullable final SomeInterface anInterface) {
            this.anInterface = anInterface;
            return this;
        }

        /**
         * Uses a supplied builder to build an instance of {@link MyRecordFOptionA} and replace the value of {@link anInterface}
         *
         * @param subBuilder Builder that can be used to replace {@code anInterface}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromInterface"},
                comments = "Related component claim: builderFluentSetter"
        )
        public Builder anInterfaceAsMyRecordFOptionA(@NonNull final Consumer<MyRecordFOptionAUtils.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final MyRecordFOptionAUtils.Builder builder;
            if (Objects.nonNull(this.anInterface()) && this.anInterface() instanceof MyRecordFOptionA oth) {
                builder = MyRecordFOptionAUtils.Builder.from(oth);
            } else {
                builder = MyRecordFOptionAUtils.Builder.builder();
            }
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.anInterface(builder.build());
        }

        /**
         * Uses a supplied builder to build an instance of {@link MyRecordFOptionB} and replace the value of {@link anInterface}
         *
         * @param subBuilder Builder that can be used to replace {@code anInterface}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromInterface"},
                comments = "Related component claim: builderFluentSetter"
        )
        public Builder anInterfaceAsMyRecordFOptionB(@NonNull final Consumer<MyRecordFOptionBUtils.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final MyRecordFOptionBUtils.Builder builder;
            if (Objects.nonNull(this.anInterface()) && this.anInterface() instanceof MyRecordFOptionB oth) {
                builder = MyRecordFOptionBUtils.Builder.from(oth);
            } else {
                builder = MyRecordFOptionBUtils.Builder.builder();
            }
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.anInterface(builder.build());
        }

        /**
         * Creates a new instance of {@link MyRecordG} from the fields set on this builder
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddPlainBuild"},
                comments = "Related class claim: builderBuild"
        )
        public MyRecordG build() {
            // "Creating new instance"
            return new MyRecordG(
                    this.anInterface(),
                    	this.listOfInterface()
                    );
        }

        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddGetter"},
                comments = "Related component claim: builderGetter"
        )
        public List<SomeInterface> listOfInterface() {
            return this.listOfInterface;
        }

        /**
         * Updates the value of {@code listOfInterface}
         * <p>
         * Supplying a null value will set the current value to null/empty
         *
         * @param listOfInterface The replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddSetter"},
                comments = "Related component claim: builderPlainSetter"
        )
        public Builder listOfInterface(@Nullable final List<SomeInterface> listOfInterface) {
            this.listOfInterface.clear();
            if (Objects.nonNull(listOfInterface)) {
                this.listOfInterface.addAll(listOfInterface);
            }
            return this;
        }
    }

    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.AllInterfaceGenerator"},
            comments = "Related class claim: allIface"
    )
    public interface All extends With {
    }

    /**
     * An interface that provides the ability to create new instances of a record with modifications
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WitherPrismInterfaceFactory"},
            comments = "Related class claim: wither"
    )
    interface With extends _MatchingInterface {
        /**
         * Creates a builder with the current fields
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BackToBuilder"},
                comments = "Related class claim: witherToBuilder"
        )
        default Builder with() {
            return Builder.builder()
                    .anInterface(this.anInterface())
                    .listOfInterface(this.listOfInterface());
        }

        /**
         * Allows creation of a copy of this instance with some tweaks via a builder
         *
         * @param subBuilder A function to modify a new copy of the object
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BuilderFluent"},
                comments = "Related class claim: witherFluentBuilder"
        )
        default MyRecordG with(@NonNull final Consumer<Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final Builder ___builder = this.with();
            subBuilder.accept(___builder);
            return ___builder.build();
        }

        /**
         * Return a new instance with a different {@code listOfInterface} field
         *
         * @param listOfInterface Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithAdd"},
                comments = "Related component claim: witherWithAdd"
        )
        default MyRecordG withAddListOfInterface(final List<SomeInterface> listOfInterface) {
            return this.with()
                    .addListOfInterface(listOfInterface)
                    .build();
        }

        /**
         * Return a new instance with a different {@code anInterface} field
         *
         * @param anInterface Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
                comments = "Related component claim: witherWith"
        )
        default MyRecordG withAnInterface(final SomeInterface anInterface) {
            return this.with()
                    .anInterface(anInterface)
                    .build();
        }

        /**
         * Return a new instance with a different {@code listOfInterface} field
         *
         * @param listOfInterface Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
                comments = "Related component claim: witherWith"
        )
        default MyRecordG withListOfInterface(final List<SomeInterface> listOfInterface) {
            return this.with()
                    .listOfInterface(listOfInterface)
                    .build();
        }
    }

    @NullUnmarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceFactory"},
            comments = "Related component claim: internalMatchingIface"
    )
    interface _MatchingInterface {
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
                comments = "Related component claim: internalMatchingIface"
        )
        SomeInterface anInterface();

        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
                comments = "Related component claim: internalMatchingIface"
        )
        List<SomeInterface> listOfInterface();
    }
}
If you only annotate one of the records, then a *Utils class won’t be generated for the omitted record, and both the interface’s and MyRecordG generated *Utils classes will only be able to reference the one record

3.6.3. Annotations from serialisation libraries

If you are using various libraries, you can use their annotations to inform the processor as to implementations of an interface.

Jakarta XML Bindings

If you are using Jakarta’s XML Binding annotations, you can use either:

  • @XmlSeeAlso on the interface, listing the implementing records

  • @XmlElements on a component, listing the implementing records

Jackson

If you are using Jackson, you can use an @JsonSubTypes on the interface, listing the implementing records.

As of 22nd August 2025 the latest version is 0.6.1, and this version of the processor doesn’t process the annotation on record components.
Avaje Jsonb

If you are using Avaje’s Jsonb, you can use an @Json.SubTypes on the interface, listing the implementing records.

3.6.4. Use package-wide application

If you annotate the package that contains the structure like so:

package-info.java
@io.github.cbarlin.aru.annotations.AdvancedRecordUtils(applyToAllInPackage = true)
package org.example;
SomeInterface.java
public interface SomeInterface {
}
MyRecordFOptionA.java
public record MyRecordFOptionA(
    String someStrField
) implements SomeInterface {
}
MyRecordFOptionB.java
public record MyRecordFOptionB(
    int someIntField
) implements SomeInterface {
}
MyRecordG.java
public record MyRecordG(
    SomeInterface anInterface
) {}

Then when the processor reads the interface defined on both of the records it is able to incorporate it into its internal workflows. This means it will build *Utils classes for:

  • SomeInterface

  • MyRecordFOptionA

  • MyRecordFOptionB

The below collapsible is the same as the one under "Seal the interface"
Generated *Utils classes for the interface
SomeInterfaceUtils
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import org.jspecify.annotations.NullMarked;

/**
 * The Utils class for an interface. Serves mostly to point to concrete implementationsAn auto-generated utility class to work with {@link SomeInterface} objects
 * <p>
 * This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
 * <p>
 * For more details, see the GitHub page for cbarlin/advanced-record-utils
 */
@NullMarked
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
        generatedFor = SomeInterface.class,
        version = @AdvancedRecordUtilsGenerated.Version(
                major = 0,
                minor = 6,
                patch = 0
        ),
        settings = @AdvancedRecordUtils(
                builderOptions = @AdvancedRecordUtils.BuilderOptions(copyCreationName = "from", builtCollectionType = AdvancedRecordUtils.BuiltCollectionType.AUTO),
                attemptToFindExistingUtils = true
        ),
        internalUtils = {

        },
        references = {
            MyRecordFOptionAUtils.class,
            MyRecordFOptionBUtils.class
        },
        usedTypeConverters = {

        }
)
public final class SomeInterfaceUtils implements GeneratedUtil {
    /**
     * Obtain the builder for {@link MyRecordFOptionA}
     */
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderEmpty"},
            comments = "Related class claim: builderEmpty"
    )
    public static final MyRecordFOptionAUtils.Builder builderAsMyRecordFOptionA() {
        return MyRecordFOptionAUtils.Builder.builder();
    }

    /**
     * Obtain the builder for {@link MyRecordFOptionB}
     */
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderEmpty"},
            comments = "Related class claim: builderEmpty"
    )
    public static final MyRecordFOptionBUtils.Builder builderAsMyRecordFOptionB() {
        return MyRecordFOptionBUtils.Builder.builder();
    }

    /**
     * Obtain the builder for {@link MyRecordFOptionA}
     *
     * @param existing The instance to copy
     */
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderCopy"},
            comments = "Related class claim: builderCopy"
    )
    public static final MyRecordFOptionAUtils.Builder fromAsMyRecordFOptionA(final MyRecordFOptionA existing) {
        return MyRecordFOptionAUtils.Builder.from(existing);
    }

    /**
     * Obtain the builder for {@link MyRecordFOptionB}
     *
     * @param existing The instance to copy
     */
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderCopy"},
            comments = "Related class claim: builderCopy"
    )
    public static final MyRecordFOptionBUtils.Builder fromAsMyRecordFOptionB(final MyRecordFOptionB existing) {
        return MyRecordFOptionBUtils.Builder.from(existing);
    }
}
MyRecordGUtils
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.Consumer;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;

/**
 * An auto-generated utility class to work with {@link MyRecordG} objects
 * <p>
 * This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
 * <p>
 * For more details, see the GitHub page for cbarlin/advanced-record-utils
 */
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
        generatedFor = MyRecordG.class,
        version = @AdvancedRecordUtilsGenerated.Version(
                major = 0,
                minor = 6,
                patch = 0
        ),
        settings = @AdvancedRecordUtils(
                builderOptions = @AdvancedRecordUtils.BuilderOptions(copyCreationName = "from", builtCollectionType = AdvancedRecordUtils.BuiltCollectionType.AUTO),
                attemptToFindExistingUtils = true
        ),
        internalUtils = {
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "All", implementation = MyRecordGUtils.All.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "Builder", implementation = MyRecordGUtils.Builder.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "With", implementation = MyRecordGUtils.With.class),
            @AdvancedRecordUtilsGenerated.InternalUtil(type = "_MatchingInterface", implementation = MyRecordGUtils._MatchingInterface.class)
        },
        references = {
            MyRecordFOptionAUtils.class,
            MyRecordFOptionBUtils.class,
            SomeInterfaceUtils.class
        },
        usedTypeConverters = {

        }
)
public final class MyRecordGUtils implements GeneratedUtil {
    /**
     * Create a blank builder of {@link MyRecordG}
     */
    @NonNull
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
            comments = "Related class claim: builderEmpty"
    )
    public static final Builder builder() {
        return Builder.builder();
    }

    /**
     * Creates a new builder of {@link MyRecordG} by copying an existing instance
     *
     * @param original The existing instance to copy
     */
    @NonNull
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
            comments = "Related class claim: builderCopy"
    )
    public static final Builder from(final MyRecordG original) {
        return Builder.from(original);
    }

    /**
     * A class used for building {@link MyRecordG} objects
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.BuilderClassCreatorVisitor"},
            comments = "Related class claim: builder"
    )
    public static final class Builder {
        @Nullable
        private SomeInterface anInterface;

        @NonNull
        private ArrayList<SomeInterface> listOfInterface = new ArrayList<SomeInterface>();

        /**
         * Create a blank builder of {@link MyRecordG}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
                comments = "Related class claim: builderEmpty"
        )
        public static final Builder builder() {
            return new Builder();
        }

        /**
         * Creates a new builder of {@link MyRecordG} by copying an existing instance
         *
         * @param original The existing instance to copy
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
                comments = "Related class claim: builderCopy"
        )
        public static final Builder from(final MyRecordG original) {
            Objects.requireNonNull(original, "Cannot copy a null instance");
            // "Copying an existing instance"
            return Builder.builder()
                    .anInterface(original.anInterface())
                    .listOfInterface(original.listOfInterface());
        }

        /**
         * Add a singular {@link SomeInterface} to the collection for the field {@code listOfInterface}
         * <p>
         * Supplying a null value will set the current value to null
         *
         * @param listOfInterface A singular instance to be added to the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAdder"},
                comments = "Related component claim: builderAdd"
        )
        public Builder addListOfInterface(@Nullable final SomeInterface listOfInterface) {
            this.listOfInterface.add(listOfInterface);
            return this;
        }

        /**
         * Adds all elements of the provided collection to {@code listOfInterface}
         *
         * @param listOfInterface A collection to be merged into the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
                comments = "Related component claim: builderAddAllIterable"
        )
        public Builder addListOfInterface(@NonNull final Collection<SomeInterface> listOfInterface) {
            if (Objects.nonNull(listOfInterface)) {
                this.listOfInterface.addAll(listOfInterface);
            }
            return this;
        }

        /**
         * Adds all elements of the provided iterable to {@code listOfInterface}
         *
         * @param listOfInterface An iterable to be merged into the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
                comments = "Related component claim: builderAddAllIterable"
        )
        public Builder addListOfInterface(@NonNull final Iterable<SomeInterface> listOfInterface) {
            if (Objects.nonNull(listOfInterface)) {
                for (final SomeInterface __addable : listOfInterface) {
                    this.addListOfInterface(__addable);
                }
            }
            return this;
        }

        /**
         * Adds all elements of the provided iterator to {@code listOfInterface}
         *
         * @param listOfInterface An iterator to be merged into the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
                comments = "Related component claim: builderAddAllIterable"
        )
        public Builder addListOfInterface(@NonNull final Iterator<SomeInterface> listOfInterface) {
            if (Objects.nonNull(listOfInterface)) {
                while(listOfInterface.hasNext()) {
                    this.addListOfInterface(listOfInterface.next());
                }
            }
            return this;
        }

        /**
         * Adds all elements of the provided spliterator to {@code listOfInterface}
         *
         * @param listOfInterface A spliterator to be merged into the collection
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
                comments = "Related component claim: builderAddAllIterable"
        )
        public Builder addListOfInterface(@NonNull final Spliterator<SomeInterface> listOfInterface) {
            if (Objects.nonNull(listOfInterface)) {
                listOfInterface.forEachRemaining(this::addListOfInterface);
            }
            return this;
        }

        /**
         * Uses a supplied builder to build an instance of {@link MyRecordFOptionA} and add to the value of {@link addListOfInterface}
         *
         * @param subBuilder Builder used to invoke {@code addListOfInterface}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddFluentAdderFromInterface"},
                comments = "Related component claim: builderFluentSetter"
        )
        public Builder addListOfInterfaceToMyRecordFOptionA(@NonNull final Consumer<MyRecordFOptionAUtils.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final MyRecordFOptionAUtils.Builder builder = MyRecordFOptionAUtils.Builder.builder();
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.addListOfInterface(builder.build());
        }

        /**
         * Uses a supplied builder to build an instance of {@link MyRecordFOptionB} and add to the value of {@link addListOfInterface}
         *
         * @param subBuilder Builder used to invoke {@code addListOfInterface}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddFluentAdderFromInterface"},
                comments = "Related component claim: builderFluentSetter"
        )
        public Builder addListOfInterfaceToMyRecordFOptionB(@NonNull final Consumer<MyRecordFOptionBUtils.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final MyRecordFOptionBUtils.Builder builder = MyRecordFOptionBUtils.Builder.builder();
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.addListOfInterface(builder.build());
        }

        /**
         * Returns the current value of {@code anInterface}
         */
        @Nullable
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
                comments = "Related component claim: builderGetter"
        )
        public SomeInterface anInterface() {
            return this.anInterface;
        }

        /**
         * Updates the value of {@code anInterface}
         * <p>
         * Supplying a null value will set the current value to null
         *
         * @param anInterface The replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
                comments = "Related component claim: builderPlainSetter"
        )
        public Builder anInterface(@Nullable final SomeInterface anInterface) {
            this.anInterface = anInterface;
            return this;
        }

        /**
         * Uses a supplied builder to build an instance of {@link MyRecordFOptionA} and replace the value of {@link anInterface}
         *
         * @param subBuilder Builder that can be used to replace {@code anInterface}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromInterface"},
                comments = "Related component claim: builderFluentSetter"
        )
        public Builder anInterfaceAsMyRecordFOptionA(@NonNull final Consumer<MyRecordFOptionAUtils.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final MyRecordFOptionAUtils.Builder builder;
            if (Objects.nonNull(this.anInterface()) && this.anInterface() instanceof MyRecordFOptionA oth) {
                builder = MyRecordFOptionAUtils.Builder.from(oth);
            } else {
                builder = MyRecordFOptionAUtils.Builder.builder();
            }
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.anInterface(builder.build());
        }

        /**
         * Uses a supplied builder to build an instance of {@link MyRecordFOptionB} and replace the value of {@link anInterface}
         *
         * @param subBuilder Builder that can be used to replace {@code anInterface}
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromInterface"},
                comments = "Related component claim: builderFluentSetter"
        )
        public Builder anInterfaceAsMyRecordFOptionB(@NonNull final Consumer<MyRecordFOptionBUtils.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final MyRecordFOptionBUtils.Builder builder;
            if (Objects.nonNull(this.anInterface()) && this.anInterface() instanceof MyRecordFOptionB oth) {
                builder = MyRecordFOptionBUtils.Builder.from(oth);
            } else {
                builder = MyRecordFOptionBUtils.Builder.builder();
            }
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.anInterface(builder.build());
        }

        /**
         * Creates a new instance of {@link MyRecordG} from the fields set on this builder
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddPlainBuild"},
                comments = "Related class claim: builderBuild"
        )
        public MyRecordG build() {
            // "Creating new instance"
            return new MyRecordG(
                    this.anInterface(),
                    	this.listOfInterface()
                    );
        }

        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddGetter"},
                comments = "Related component claim: builderGetter"
        )
        public List<SomeInterface> listOfInterface() {
            return this.listOfInterface;
        }

        /**
         * Updates the value of {@code listOfInterface}
         * <p>
         * Supplying a null value will set the current value to null/empty
         *
         * @param listOfInterface The replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddSetter"},
                comments = "Related component claim: builderPlainSetter"
        )
        public Builder listOfInterface(@Nullable final List<SomeInterface> listOfInterface) {
            this.listOfInterface.clear();
            if (Objects.nonNull(listOfInterface)) {
                this.listOfInterface.addAll(listOfInterface);
            }
            return this;
        }
    }

    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.AllInterfaceGenerator"},
            comments = "Related class claim: allIface"
    )
    public interface All extends With {
    }

    /**
     * An interface that provides the ability to create new instances of a record with modifications
     */
    @NullMarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WitherPrismInterfaceFactory"},
            comments = "Related class claim: wither"
    )
    interface With extends _MatchingInterface {
        /**
         * Creates a builder with the current fields
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BackToBuilder"},
                comments = "Related class claim: witherToBuilder"
        )
        default Builder with() {
            return Builder.builder()
                    .anInterface(this.anInterface())
                    .listOfInterface(this.listOfInterface());
        }

        /**
         * Allows creation of a copy of this instance with some tweaks via a builder
         *
         * @param subBuilder A function to modify a new copy of the object
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BuilderFluent"},
                comments = "Related class claim: witherFluentBuilder"
        )
        default MyRecordG with(@NonNull final Consumer<Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final Builder ___builder = this.with();
            subBuilder.accept(___builder);
            return ___builder.build();
        }

        /**
         * Return a new instance with a different {@code listOfInterface} field
         *
         * @param listOfInterface Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithAdd"},
                comments = "Related component claim: witherWithAdd"
        )
        default MyRecordG withAddListOfInterface(final List<SomeInterface> listOfInterface) {
            return this.with()
                    .addListOfInterface(listOfInterface)
                    .build();
        }

        /**
         * Return a new instance with a different {@code anInterface} field
         *
         * @param anInterface Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
                comments = "Related component claim: witherWith"
        )
        default MyRecordG withAnInterface(final SomeInterface anInterface) {
            return this.with()
                    .anInterface(anInterface)
                    .build();
        }

        /**
         * Return a new instance with a different {@code listOfInterface} field
         *
         * @param listOfInterface Replacement value
         */
        @NonNull
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
                comments = "Related component claim: witherWith"
        )
        default MyRecordG withListOfInterface(final List<SomeInterface> listOfInterface) {
            return this.with()
                    .listOfInterface(listOfInterface)
                    .build();
        }
    }

    @NullUnmarked
    @Generated(
            value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceFactory"},
            comments = "Related component claim: internalMatchingIface"
    )
    interface _MatchingInterface {
        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
                comments = "Related component claim: internalMatchingIface"
        )
        SomeInterface anInterface();

        @Generated(
                value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
                comments = "Related component claim: internalMatchingIface"
        )
        List<SomeInterface> listOfInterface();
    }
}

3.7. Type Alias

TODO: Write this section

3.8. Importing items

You can also import records and generate *Utils classes for them, or make use of their existing *Utils classes if they exist.

3.8.1. Importing records/interfaces to create new *Utils classes

You can import records and interfaces in any place that you can place an @AdvancedRecordUtils annotation by using the importTargets setting. This can be used to create *Utils classes for records in a library, or in a different package to the one the record is defined in.

This process will not cascade record/interface references. If you wish to import a tree of records, you will have to include all elements of the tree in your import.

An example that’s on a package-info.java (but you can use it on a record or an interface if you wish):

package-info.java
@AdvancedRecordUtils(
    importTargets = {
        ExternalDependencyRecordA.class,
        ExternalDependencyRecordB.class,
        ExternalDependencyInterface.class
    }
)
package org.example;

import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;

This will generate *Utils classes for each of the requested items, using the settings of the item doing the importing, and places them in the same package as the place they were imported.

Importing the same record or interface twice in two different packages or with two different settings will result in unreproducible and unreliable builds as which location/settings are used isn’t guaranteed.

3.8.2. Importing existing utils classes

Importing existing *Utils classes allows the following:

  • Use of a TypeConverter defined in that library and used by a *Utils class

  • Fluent methods when using the associated record in your records

When importing trees of records, you only need to import the "root" of each tree. This is because the *Utils classes include a metadata annotation that the processor can read back in to recreate the tree. That includes *Utils classes that it imported too!
A *Utils class can be imported multiple times in multiple ways without any negative effects. It can also only be imported in one place and used everywhere - their context, like @TypeConverter, is global.

There are a few ways to do this:

  1. You can place the @AdvancedRecordUtils.ImportLibraryUtils annotation on any of:

    • module-info.java

    • Any package-info.java

    • Any record, interface, or even normal class!

    When defined, you list in an array the classes of the *Utils implementations to be imported.

    An example:

    module-info.java
    import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
    
    @AdvancedRecordUtils.ImportLibraryUtils({TopLevelRecordUtils.class})
    module org.example {
        requires io.github.cbarlin.aru.annotations;
    }
  2. You can use the aforementioned importTargets and target the *Utils class instead of the record/interface.

  3. On a record that references the record/interface that has a *Utils class already generated, you can add the attemptToFindExistingUtils = true top level setting like so:

    MyCurrentRecord.java
    @AdvancedRecordUtils(
        attemptToFindExistingUtils = true
    )
    public record MyCurrentRecord(
        MyRecordFromLibrary myRecordFromLibrary
    ) {}

    Note that this method is:

    • Slow; and

    • Will only detect the *Utils that is named using either:

      • The current settings; or

      • The default settings

    It’s probably better to use either of the above methods instead

4. Configuration options

This section outlines every configuration toggle the processor understands, with defaults, trade‑offs, and cross‑references to usage examples.

4.1. General output options

These are applied on the annotation directly, such as:

Placement example
@AdvancedRecordUtils(wither = false)
wither

Enables or disables the generation of the With interface. With-methods provide an immutable way to create a new record instance with a single field changed.

Type

boolean

Default

true

Usage: Disabling the wither
@AdvancedRecordUtils(wither = false)
public record Person(String name, int age) {}

For more information on the Wither, see the earlier section. For more information on the options for the Wither, see the later section.

merger

Enables or disables the generation of the Mergeable interface (and internal _MergerUtils). This allows you to merge two instances of a record.

Type

boolean

Default

false

Usage: Enabling the merger
@AdvancedRecordUtils(merger = true)
public record Person(String name, int age) {}

For more information on the Merger, see the earlier section. For more information on the options for the Merger, see the later section.

xmlable

Enables or disables the generation of the XML interface (and internal _XmlUtils). This allows you to convert an instance of your record to XML.

Type

boolean

Default

false

Usage: Enabling the XML
@AdvancedRecordUtils(xmlable = true)
public record Person(String name, int age) {}

For more information on the XML generation, see the earlier section including the section on supported annotations. For more information on the options for the XML, see the later section.

diffable

Enables or disables the generation of the Diffable interface and diff result classes (and internal _DiffUtils). This allows you to compute and inspect differences between two instances of a record.

Type

boolean

Default

false

Usage: Enabling the Diffable generation
@AdvancedRecordUtils(diffable = true)
public record Person(String name, int age) {}

For more information on the Differ, see the earlier section. For more information on the options for the Differ, see the later section.

createAllInterface

Enables or disables the generation of the All interface. This interface extends all the generated interfaces, so you only need to import the one thing. If you implement it, the processor will seal the interfaces it generates.

Type

boolean

Default

true

Usage: Disabling the All interface
@AdvancedRecordUtils(createAllInterface = false)
public record Person(String name, int age) {}
logGeneration

Defines the type of logging performed by generated code, if any.

Type

LoggingGeneration

Default

LoggingGeneration.NONE

Usage: Choosing a setting
@AdvancedRecordUtils(logGeneration = LoggingGeneration.SLF4J_GENERATED_UTIL_INTERFACE)
public record Person(String name, int age) {}

Valid values are:

  • NONE (Default) - no logs are generated (instead, comments are written into the generated code)

  • SLF4J_GENERATED_UTIL_INTERFACE - Use the SLF4J library to write logs as if they came from the GeneratedUtil interface

  • SLF4J_UTILS_CLASS - Use the SLF4J library to write logs as if they all came from the top-level *Utils class

  • SLF4J_PER_SUBCLASS - Use the SLF4J library to write logs on a per-subclass basis

Using the SLF4J_GENERATED_UTIL_INTERFACE option can make it easier to control the logging of all generated code by setting the level of the io.github.cbarlin.aru.annotations package.

When enabled, the generated logs include a number of attributes in order to make them easily filterable. These are:

  • advancedRecordUtilsProcessor - a constant, "io.github.cbarlin.aru.core.AdvRecUtilsProcessor"

  • originalType - this is the FQN of the record class that we are generating code for

  • intendedType - this is the type that the builder will build for you. It may be the same as "originalType". See useInterface option

  • advancedRecordUtilsVisitor - an internal reference for AdvancedRecordUtils maintainers. It’s the FQN of the class that generated the method (and thus, the log line)

  • claimedOperationName - an internal reference. It’s the "claim" that was obtained to generate the method

  • claimedOperationType - for the aforementioned claim, was it made at a class or component level

Generated logging line within a method
__LOGGER.atTrace()
    .addKeyValue(LoggingConstants.VISITOR_NAME_KEY, "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction")
    .addKeyValue(LoggingConstants.PROCESSOR_NAME_KEY, LoggingConstants.PROCESSOR_NAME_VALUE)
    .addKeyValue(LoggingConstants.CLAIM_OP_NAME_KEY, "builderCopy")
    .addKeyValue(LoggingConstants.CLAIM_OP_TYPE_KEY, LoggingConstants.CLAIM_OP_TYPE_VALUE_CLASS)
    .addKeyValue(LoggingConstants.INTENDED_TYPE_KEY, "io.github.cbarlin.aru.tests.cdncl.RootItem")
    .addKeyValue(LoggingConstants.ORIGINAL_TYPE_KEY, "io.github.cbarlin.aru.tests.cdncl.RootItem")
    .log("Copying an existing instance");
Structure when using SLF4J_GENERATED_UTIL_INTERFACE
public final class PersonUtils implements GeneratedUtil {
    private static final Logger __LOGGER = LoggerFactory.getLogger(GeneratedUtil.class);

    public static final class Builder {
        // No logger here
    }
}
Structure when using SLF4J_UTILS_CLASS
public final class PersonUtils implements GeneratedUtil {
    private static final Logger __LOGGER = LoggerFactory.getLogger(PersonUtils.class);

    public static final class Builder {
        // No logger here
    }
}
Structure when using SLF4J_PER_SUBCLASS
public final class PersonUtils implements GeneratedUtil {
    private static final Logger __LOGGER = LoggerFactory.getLogger(PersonUtils.class);

    public static final class Builder {
        private static final Logger __LOGGER = LoggerFactory.getLogger(PersonUtils.Builder.class);
    }
}
addJsonbImportAnnotation

Enables or disables the generation of an Avaje @Json.Import annotation on the *Utils classes.

Type

boolean

Default

false

Usage: Add to any place you can add the annotation
@AdvancedRecordUtils(addJsonbImportAnnotation = true)
public record Person(String name, int age) {}

Particularly useful on large trees of records, as the cascading effect means you do not have to annotate each record. The processor also automatically handles interfaces.

When generating for interfaces, the annotation uses Avaje’s default convention for type determination.
Generated annotation for record *Utils classes
@Json.Import({RootItem.class})
public final class RootItemUtils implements GeneratedUtil {

}
Generated annotation for interface *Utils classes
@Json.Import(
        value = {MyInterface.class},
        subtypes = {
        	@Json.SubType(type = MyImplA.class),
        	@Json.SubType(type = MyImplB.class),
        	@Json.SubType(type = MyImplC.class),
        }
)
public final class MyInterfaceUtils implements GeneratedUtil {

}

4.2. Scope control options

These are applied on the annotation directly, such as:

Placement example
@AdvancedRecordUtils(recursiveExcluded = {RecordToMakeExcluded.class})
recursiveExcluded

When cascading, do not include the listed classes.

Type

Class<?>[]

Default

{}

Usage: Place on any item in which cascading imports are possible
public record Address(String state) {}

@AdvancedRecordUtils(recursiveExcluded = {Address.class})
public record Person(String name, int age, Address address) {}
This does not apply globally, only to the current element being analysed and everything that cascades from it. If there are two annotations that from the originating element can form a path to a record, and you put the exclusion on one of them, then that record will still be detected from the other annotation.
importTargets

Allows the importing of elements into the current generation scope.

Type

Class<?>[]

Default

{}

Usage: Place on any package or record
@AdvancedRecordUtils(
    importTargets = {
        ExternalDependencyRecordA.class,
        ExternalDependencyRecordB.class,
        ExternalDependencyInterface.class
    }
)
package org.example;

See the earlier section on importing, particularly the section on Importing records/interfaces to create new *Utils classes.

applyToAllInPackage

Allows package-wide *Utils generation.

Type

boolean

Default

false

Usage: Place on any package
@AdvancedRecordUtils(
    applyToAllInPackage = true
)
package org.example;

See the earlier section on Automatically applying to all items in a package for more details.

attemptToFindExistingUtils

Request that when the processor is evaluating a record as part of its cascading evaluation, that it first attempts to find an existing *Utils class.

Type

boolean

Default

false

Usage: Place on any package or record
@AdvancedRecordUtils(attemptToFindExistingUtils = true)
package org.example;

See the earlier section on importing, particularly the section on Importing existing utils classes.

useInterface

Request that when generating builders, withers, etc that the return type is instead an interface that the record implements, rather than the record’s type itself.

Type

Class<?>

Default

DEFAULT.class (internal sentinel value for "disabled")

Usage: Place on any record that implements an interface
public interface MyInterface {

}

@AdvancedRecordUtils(useInterface = MyInterface.class)
public record MyInterfaceImpl() implements MyInterface {}

4.3. Top-level naming options

These control the name of the top‑level *Utils class. Apply them via the typeNameOptions = @TypeNameOptions() sub‑option, like so:

Usage: Changing the top-level name
@AdvancedRecordUtils(
    typeNameOptions = @TypeNameOptions(
        utilsImplementationPrefix = "UtilFor",
        utilsImplementationSuffix = ""
    )
)
public record Person(String name, int age) {}

There are only two options:

utilsImplementationSuffix

A suffix to be applied to the record/interface name for the top-level class

Type

String

Default

"Utils"

utilsImplementationPrefix

A prefix to be applied to the record/interface name for the top-level class

Type

String

Default

"" (empty string)

For the above settings, instead of generating a PersonUtils class, this will generate a UtilForPerson class.

4.4. Builder options

These control the methods available on, and the behaviour of, the generated Builder subclass. These are applied to the builderOptions = @BuilderOptions() sub setting like so:

Placement example
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        // Options go here
    )
)
public record Person(String name, int age) {}
The generated With and Mergeable interfaces delegate all usage to the Builder, so all settings here impact those. In addition, the With interface creates some methods based on the methods being generated for the Builder.

4.4.1. Naming options

builderName

Set the name of the Builder subclass.

Type

String

Default

"Builder"

Usage
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        builderName = "Factory"
    )
)
public record Person(String name, int age) {}

In the above example, the generated "Builder" would be found at PersonUtils.Factory:

Sample method body
PersonUtils.Factory personFactory = PersonUtils.builder();

The default looks like so:

Sample method body
PersonUtils.Builder personBuilder = PersonUtils.builder();
emptyCreationName

Set the name of the no-arg static method that creates an empty instance of the "Builder" class.

Type

String

Default

"builder"

Usage
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        emptyCreationName = "factory"
    )
)
public record Person(String name, int age) {}

In the above example, the generated "Builder" can be created by using the method named factory

Sample method body
PersonUtils.Builder personBuilder = PersonUtils.factory();

The default looks like so:

Sample method body
PersonUtils.Builder personBuilder = PersonUtils.builder();
copyCreationName

Set the name of the static method that creates an instance of the "Builder" class by copying from an existing instance.

Type

String

Default

"builder"

Usage
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        copyCreationName = "from"
    )
)
public record Person(String name, int age) {}

In the above example, the generated "Builder" can copy an existing instance using the from method like so:

Sample method body
Person personA = // create an instance
PersonUtils.Builder personBuilder = PersonUtils.from(personA);

The default looks like so:

Sample method body
Person personA = // create an instance
PersonUtils.Builder personBuilder = PersonUtils.builder(personA);
buildMethodName

Set the name of the method that the "Builder" has to create a new instance based on its internal state

Type

String

Default

"build"

Usage
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        buildMethodName = "make"
    )
)
public record Person(String name, int age) {}

In the above example, the generated "Builder" creates a new instance using a method named make like so:

Sample method body
Person personA = PersonUtils.builder()
    .make();

The default looks like so:

Sample method body
Person personA = PersonUtils.builder()
    .build();
adderMethodPrefix / adderMethodSuffix

Set the prefix and suffix that are joined with the name of a collection component for “add”-er methods on the “Builder”.

Type

String

Default

"add" (prefix), "" (suffix)

Usage
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        adderMethodPrefix = "",
        adderMethodSuffix = "Append"
    )
)
public record Person(Set<PhoneNumber> phoneNumbers) {}

In the above example, the generated "Builder" contains a method called phoneNumbersAppend that will add a new PhoneNumber to the builder

Sample method body
PersonUtils.builder()
    .phoneNumbersAppend(new PhoneNumber());

The default looks like so:

Sample method body
PersonUtils.builder()
    .addPhoneNumbers(new PhoneNumber());
setTimeNowMethodPrefix / setTimeNowMethodSuffix

Set the prefix and suffix that are joined with the name of a time component for setting its value to the current time.

Type

String

Default

"set" (prefix), "ToNow" (suffix)

Usage
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        setTimeNowMethodPrefix = "update",
        setTimeNowMethodSuffix = "ToPresent"
    )
)
public record Person(OffsetDateTime dateOfBirth) {}

In the above example, the generated “Builder” contains a method called updateDateOfBirthToPresent that will set the dateOfBirth value to the present time:

Sample method body
PersonUtils.builder()
    .updateDateOfBirthToPresent();

The default looks like so:

Sample method body
PersonUtils.builder()
    .setDateOfBirthToNow();
multiTypeSetterBridge

Set the bridge between the name of a component and the name of an implementing class on the setter, if the component targets an interface and we know the implementing records.

Type

String

Default

"As"

Usage
public sealed interface MyInterface permits ImplA, ImplB {}

@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        multiTypeSetterBridge = "OfKind"
    )
)
public record Person(MyInterface metadata) {}

In the above example, the generated “Builder” contains methods called metadataOfKindImplA and metadataOfKindImplB that only allow passing instances of that particular record:

Sample method body
PersonUtils.builder()
    .metadataOfKindImplA(new ImplA());

The default looks like so:

Sample method body
PersonUtils.builder()
    .metadataAsImplA(new ImplA());

This is particularly useful for using fluent nested builders.

Sample method body
PersonUtils.builder()
    .metadataAsImplA(implABuilder -> implABuilder.someField("a"));
multiTypeAdderBridge

Set the bridge between the name of a component and the name of an implementing class on the adder, if the component targets a collection of an interface and we know the implementing records.

Type

String

Default

"To"

Usage
public sealed interface MyInterface permits ImplA, ImplB {}

@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        multiTypeAdderBridge = "In"
    )
)
public record Person(Set<MyInterface> metadata) {}

In the above example, the generated “Builder” contains methods called addImplAInMetadata and addImplBInMetadata that only allow passing instances of that particular record:

Sample method body
PersonUtils.builder()
    .addImplAInMetadata(new ImplA());

The default looks like so:

Sample method body
PersonUtils.builder()
    .addImplAToMetadata(new ImplA());

This is particularly useful for using fluent nested builders.

Sample method body
PersonUtils.builder()
    .addImplAToMetadata(implABuilder -> implABuilder.someField("a"));

4.4.2. Generation options

fluent

Determine if fluent versions of methods should be built. These are methods that take a lambda with a single argument, which is the referenced type’s builder.

Type

boolean

Default

"true"

Usage
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        fluent = false
    )
)
public record Person(Address address, List<PhoneNumber> phoneNumbers) {}

public record Address(String country) {}

public record PhoneNumber(String number) {}

With the default setting of true, you can set the "address" field like so:

Sample method body
PersonUtils.builder()
    .address(addressBuilder -> addressBuilder.country("Australia"));

You can also add to the "phoneNumbers" field (assuming you haven’t disabled that functionality) like so:

Sample method body
PersonUtils.builder()
    .addPhoneNumbers(builder -> builder.number("04 4444 4444"));

The processor also handles self-referencing types easily. Take the following definition:

MyRecordB.java
@AdvancedRecordUtils(merger = true)
public record MyRecordB(
    String aField,
    MyRecordB recursionFtw // The processor deals with self-referencing types
) {

}

You can then do:

Sample method body
MyRecordBUtils.builder()
    .recursionFtw(
        b1 -> b1.aField("Nesting 1")
            .recursionFtw(
                b2 -> b2.aField("Nesting 2")
                    .recursionFtw(
                        b3 -> b3.aField("Nesting 3")
                    )
            )
    );
Generated code within the "Builder" class
public final class PersonUtils implements GeneratedUtil {
    public static final class Builder {
        public Builder addPhoneNumbers(@NonNull final Consumer<PhoneNumberUtils.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final PhoneNumberUtils.Builder builder = PhoneNumberUtils.Builder.builder();
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.addPhoneNumbers(builder.build());
        }

        public Builder address(@NonNull final Consumer<AddressUtils.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final AddressUtils.Builder builder = (Objects.isNull(this.address())) ? AddressUtils.Builder.builder() : AddressUtils.Builder.builder(this.address());
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.address(builder.build());
        }
    }
}
Generated code with interface

The below code assumes that the "Address" is an interface with a "PostalAddress" implementation.

public final class PersonUtils implements GeneratedUtil {
    public static final class Builder {
        public Builder addressAsPostalAddress(@NonNull final Consumer<PostalAddress.Builder> subBuilder) {
            Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
            final PostalAddress.Builder builder;
            if (Objects.nonNull(this.address()) && this.address() instanceof PostalAddress oth) {
                builder = PostalAddress.Builder.builder(oth);
            } else {
                builder = PostalAddress.Builder.builder();
            }
            // "Passing over to provided consumer"
            subBuilder.accept(builder);
            return this.address(builder.build());
        }
    }
}
createAdderMethods

Should "add"-er type methods be created on the Builder?

Type

boolean

Default

"true"

Usage
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        createAdderMethods = false
    )
)
public record Person(Address address, List<PhoneNumber> phoneNumbers) {}

public record Address(String country) {}

public record PhoneNumber(String number) {}

With the default setting of true, you can add to the "phoneNumbers" field like so:

Sample method body
PersonUtils.builder()
    .addPhoneNumbers(new PhoneNumber("04 4444 4444"));
Generated code within the "Builder" class (default settings)
public final class PersonUtils implements GeneratedUtil {
    public static final class Builder {

        public Builder addPhoneNumbers(@Nullable final PhoneNumber phoneNumbers) {
            this.phoneNumbers.add(phoneNumbers);
            return this;
        }

        public Builder addPhoneNumbers(@NonNull final Collection<PhoneNumber> phoneNumbers) {
            if (Objects.nonNull(phoneNumbers)) {
                this.phoneNumbers.addAll(phoneNumbers);
            }
            return this;
        }

        public Builder addPhoneNumbers(@NonNull final Iterable<PhoneNumber> phoneNumbers) {
            if (Objects.nonNull(phoneNumbers)) {
                for (final PhoneNumber __addable : phoneNumbers) {
                    this.addPhoneNumbers(__addable);
                }
            }
            return this;
        }

        public Builder addPhoneNumbers(@NonNull final Iterator<PhoneNumber> phoneNumbers) {
            if (Objects.nonNull(phoneNumbers)) {
                while(phoneNumbers.hasNext()) {
                    this.addPhoneNumbers(phoneNumbers.next());
                }
            }
            return this;
        }

        public Builder addPhoneNumbers(@NonNull final Spliterator<PhoneNumber> phoneNumbers) {
            if (Objects.nonNull(phoneNumbers)) {
                phoneNumbers.forEachRemaining(this::addPhoneNumbers);
            }
            return this;
        }
    }
}
Generated code within the "Builder" class (with buildNullCollectionToEmpty = false)
public final class PersonUtils implements GeneratedUtil {
    public static final class Builder {

        public Builder addPhoneNumbers(@Nullable final PhoneNumber phoneNumbers) {
            if (Objects.isNull(this.phoneNumbers)) {
                this.phoneNumbers = new ArrayList<PhoneNumber>();
            } else if (!(this.phoneNumbers instanceof ArrayList)) {
                this.phoneNumbers = new ArrayList<PhoneNumber>(this.phoneNumbers);
            }
            this.phoneNumbers.add(phoneNumbers);
            return this;
        }

        public Builder addPhoneNumbers(@NonNull final Iterable<PhoneNumber> phoneNumbers) {
            if (Objects.nonNull(phoneNumbers)) {
                for (final PhoneNumber __addable : phoneNumbers) {
                    this.addPhoneNumbers(__addable);
                }
            }
            return this;
        }

        public Builder addPhoneNumbers(@NonNull final Iterable<PhoneNumber> phoneNumbers) {
            if (Objects.nonNull(phoneNumbers)) {
                for (final PhoneNumber __addable : phoneNumbers) {
                    this.addPhoneNumbers(__addable);
                }
            }
            return this;
        }

        public Builder addPhoneNumbers(@NonNull final Iterator<PhoneNumber> phoneNumbers) {
            if (Objects.nonNull(phoneNumbers)) {
                while(phoneNumbers.hasNext()) {
                    this.addPhoneNumbers(phoneNumbers.next());
                }
            }
            return this;
        }

        public Builder addPhoneNumbers(@NonNull final Spliterator<PhoneNumber> phoneNumbers) {
            if (Objects.nonNull(phoneNumbers)) {
                phoneNumbers.forEachRemaining(this::addPhoneNumbers);
            }
            return this;
        }
    }
}
nullReplacesNotNull

Should the builder allow setting a "null" value if there’s already a non-null value?

Type

boolean

Default

"true"

Usage
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        nullReplacesNotNull = false
    )
)
public record Person(Address address, List<PhoneNumber> phoneNumbers) {}

public record Address(String country) {}

public record PhoneNumber(String number) {}

If you set this to false, the below will pass:

Sample method body
PersonUtils.Builder builder = PersonUtils.builder()
    .address(new Address("Australia"))
    .address(null);
// It's not null on the builder object
Objects.requireNonNull(builder.address());
// And it's not null when built
Objects.requireNonNull(builder.build().address());
Generated code within the "Builder" class when false
public final class PersonUtils implements GeneratedUtil {
    public static final class Builder {
        public Builder address(@Nullable final Address address) {
            this.address = Objects.nonNull(address) ? address : this.address;
            return this;
        }
    }
}
Generated code within the "Builder" class when true (default)
public final class PersonUtils implements GeneratedUtil {
    public static final class Builder {
        public Builder address(@Nullable final Address address) {
            this.address = address;
            return this;
        }
    }
}
builtCollectionType

If the Builder points to a generic collection type (such as List or Set), what should the builder construct when building?

If you define a concrete type the Builder will attempt to respect that type (e.g. specifying a LinkedList).
Type

BuiltCollectionType

Default

BuiltCollectionType.JAVA_IMMUTABLE

Usage
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        builtCollectionType = AdvancedRecordUtils.BuiltCollectionType.AUTO
    )
)
public record Person(Address address, List<PhoneNumber> phoneNumbers, Set<String> names) {}

public record Address(String country) {}

public record PhoneNumber(String number) {}

It has the following values:

  • AUTO - the collection will be whatever type is passed into the builder, unless you attempt to call an "add"-er method, in which case a sane default will be chosen (ArrayList, HashSet, etc)

  • JAVA_IMMUTABLE - the resulting collection will always be immutable

Generated code within the "Builder" class when set to JAVA_IMMUTABLE (default)
public final class PersonUtils implements GeneratedUtil {
    public static final class Builder {

        public Set<String> names() {
            final Set<String> ___immutable = this.names.stream()
                        .filter(Objects::nonNull)
                        .collect(Collectors.toUnmodifiableSet());
            return ___immutable;
        }

        public List<PhoneNumber> phoneNumbers() {
            final List<PhoneNumber> ___immutable = this.phoneNumbers.stream()
                        .filter(Objects::nonNull)
                        .toList();
            return ___immutable;
        }

        public Set<ExampleEnum> exampleEnum() {
            // Usually you use Set.copyOf, but that internally uses a slower HashSet. This keeps the speed of an EnumSet, and maintains deep immutability by using a collection that can't be used elsewhere
            final EnumSet<ExampleEnum> ___copy = EnumSet.copyOf(this.exampleEnum);
            return Collections.unmodifiableSet(___copy);
        }
    }
}
Generated code within the "Builder" class when set to AUTO
public final class PersonUtils implements GeneratedUtil {
    public static final class Builder {

        public Set<String> names() {
            return this.names;
        }

        public List<PhoneNumber> phoneNumbers() {
            return this.phoneNumbers;
        }

        public Set<ExampleEnum> exampleEnum() {
            return this.exampleEnum;
        }
    }
}
buildNullCollectionToEmpty

If the Builder is going to result in a null value for a collection, should we instead return an empty collection?

Type

boolean

Default

true

Usage
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        buildNullCollectionToEmpty = false
    )
)
public record Person(Address address, List<PhoneNumber> phoneNumbers) {}

public record Address(String country) {}

public record PhoneNumber(String number) {}
Instead of modifying the "getter" for the builder, this instead modifies the default value of the internal field when the builder is constructed as well as the "setter" to have different behaviour if you pass in null.
Generated code within the "Builder" class when true (default)
public final class PersonUtils implements GeneratedUtil {
    public static final class Builder {
        @NonNull
        private ArrayList<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();

        public Builder phoneNumbers(@Nullable final List<PhoneNumber> phoneNumbers) {
            this.phoneNumbers.clear();
            if (Objects.nonNull(phoneNumbers)) {
                this.phoneNumbers.addAll(phoneNumbers);
            }
            return this;
        }
    }
}
Generated code within the "Builder" class when false
public final class PersonUtils implements GeneratedUtil {
    public static final class Builder {
        @Nullable
        private List<PhoneNumber> phoneNumbers;

        public Builder phoneNumbers(@Nullable final List<PhoneNumber> phoneNumbers) {
            this.phoneNumbers = phoneNumbers;
            return this;
        }
    }
}
setTimeNowMethods

Should the builder contain methods that automatically set OffsetDateTime, ZonedDateTime, and LocalDateTime to the current time?

Type

boolean

Default

true

Usage
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        setTimeNowMethods = false
    )
)
public record Person(
    OffsetDateTime offsetDateTime,
    ZonedDateTime zonedDateTime,
    LocalDateTime localDateTime
) {
}
Generated code within the "Builder" class when true
public final class PersonUtils implements GeneratedUtil {
    public static final class Builder {

        public Builder setLocalDateTimeToNow() {
            return this.localDateTime(LocalDateTime.now());
        }

        public Builder setOffsetDateTimeToNow() {
            return this.offsetDateTime(OffsetDateTime.now());
        }

        public Builder setZonedDateTimeToNow() {
            return this.zonedDateTime(ZonedDateTime.now());
        }
    }
}
setToNullMethods

Should the builder generate methods to explicitly reset a field to a “null-like” state?

The result of the method is:

  • empty for Optional components

  • empty for collection components when buildNullCollectionToEmpty is true

  • null for all other component types.

    Type

    boolean

    Default

    false

When nullReplacesNotNull = false, these setXToNull methods will not be generated at all.
Usage
@AdvancedRecordUtils(
    builderOptions = @AdvancedRecordUtils.BuilderOptions(
        setToNullMethods = true
    )
)
public record Person(
    OffsetDateTime offsetDateTime,
    List<String> tags,
    String name
) { }
Generated code
public final class PersonUtils implements GeneratedUtil {
    public static final class Builder {
        public final Builder setNameToNull() {
            this.name = null;
            return this;
        }

        public final Builder setOffsetDateTimeToNull() {
            this.offsetDateTime = null;
            return this;
        }

        public final Builder setTagsToNull() {
            this.tags.clear();
            return this;
        }
    }
}
concreteSettersForOptional

If an optional type is used, should there be a setter that takes the concrete type?

Type

boolean

Default

true

Usage
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        concreteSettersForOptional = false
    )
)
public record Person(
    OptionalInt age,
    Optional<String> surname
) {
}

If set to true (default), you are able to do the following:

Sample method body
PersonUtils.builder()
    .age(42)
    .surname((String) null);
Generated code within the "Builder" class when true
public final class PersonUtils implements GeneratedUtil {
    public static final class Builder {

        public OptionalInt age() {
            return Objects.requireNonNullElse(this.age, OptionalInt.empty());
        }

        public Builder age(final int age) {
            return this.age(OptionalInt.of(age));
        }

        public Builder age(@Nullable final OptionalInt age) {
            this.age = age;
            return this;
        }

        public Optional<String> surname() {
            return Objects.requireNonNullElse(this.surname, Optional.empty());
        }

        public Builder surname(@Nullable final String surname) {
            return this.surname(Optional.ofNullable(surname));
        }

        public Builder surname(@Nullable final Optional<String> surname) {
            this.surname = surname;
            return this;
        }
    }
}
validatedBuilder

Should there be versions of the build methods that validate the built item?

Type

ValidationApi

Default

ValidationApi.NONE

Usage
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        validatedBuilder = ValidationApi.AVAJE
    )
)
public record Person(
    @NonNull @NotBlank @Size(max = 50)
    String name
) {
}

There are three possible values, with the default NONE not generating any additional methods.

Use of any value other than NONE will need the necessary dependencies.

The value of JAKARTA_PLAIN generates methods that allow you to use Jakarta’s validation API directly in order to validate the built object like so:

Sample method body
Validator validator = obtainValidator(); // You will need a validator
PersonUtils.builder()
    .build(
        validator,
        (Set<Set<ConstraintViolation<Person>>> violations) -> {
            // This method is only called if there's at least one violation
        }
    );
// Alternatively, you can do:
PersonUtils.builder()
    .build(
        validator,
        (Set<Set<ConstraintViolation<Person>>> violations, Person personThatWasBuilt) -> {
            // This method is always called
        }
    );

The value of AVAJE generates methods that allows you to use the Avaje Validator. You can use it like so:

Sample method body
Validator validator = obtainValidator(); // You will need a validator
PersonUtils.Builder builder = PersonUtils.builder();
try {
    // These use your validator:
    builder.build(validator);
    builder.build(validator, GroupA.class, GroupB.class);
    // These use the default validator
    builder.buildAndValidate();
    builder.buildAndValidate(GroupA.class, GroupB.class);
    // And you can still use the original builder:
    builder.build();
} catch (ConstraintViolationException e) {
    // Do something with the violations
}
Generated code when using JAKARTA_PLAIN
public final class PersonUtils implements GeneratedUtil {
    public static final class Builder {
        public Person build() {
            // "Creating new instance"
            return new Person(
                    this.name()
                    );
        }

        public Person build(@NonNull final Validator validator, @NonNull final Consumer<Set<ConstraintViolation<Person>>> resultConsumer) {
            final Person built = this.build();
            final Set<ConstraintViolation<Person>> validationResults = validator.validate(built);
            if (Objects.nonNull(validationResults) && (!validationResults.isEmpty())) {
                resultConsumer.accept(validationResults);
            }
            return built;
        }

        public Person build(@NonNull final Validator validator,
                @NonNull final BiConsumer<Set<ConstraintViolation<Person>>, Person> resultConsumer) {
            final Person built = this.build();
            final Set<ConstraintViolation<Person>> validationResults = validator.validate(built);
            resultConsumer.accept(validationResults, built);
            return built;
        }
    }
}
Generated code when using AVAJE
public final class PersonUtils implements GeneratedUtil {
    public static final class Builder {
        public Person build() {
            // "Creating new instance"
            return new Person(
                    this.name()
                    );
        }

        public Person build(@NonNull final Validator validator, @Nullable final Class<?>... groups) throws ConstraintViolationException {
            final Person built = this.build();
            validator.validate(built, groups);
            return built;
        }

        public Person buildAndValidate(@Nullable final Class<?>... groups) throws ConstraintViolationException {
            return this.build(Validator.builder().build(), groups);
        }
    }
}
mapStructValidatesWithAvaje

When using both the Avaje validator (see validation settings) and MapStruct, should we request that MapStruct calls the method that uses the default validator?

Type

boolean

Default

false

Usage
@AdvancedRecordUtils(
    builderOptions = @BuilderOptions(
        validatedBuilder = ValidationApi.AVAJE,
        mapStructValidatesWithAvaje = true
    )
)
@Valid
public record Person(
    @NonNull @NotBlank @Size(max = 50)
    String name
) {
}

When set to true and the "buildAndValidate" method has been created then the MapStruct integration will call that method. If false, or if there’s no "buildAndValidate" method, then it will use the default "build" method.

4.5. Wither options

These control the naming of the generated With interface, and its methods. Apply them via the witherOptions = @WitherOptions() sub‑setting:

Placement example
@AdvancedRecordUtils(
    witherOptions = @WitherOptions(
        // Options go here
    )
)
public record Person(String name, int age) {}
witherName

Sets the name of the With interface.

Type

String

Default

"With"

Usage: Changing the name of the interface
@AdvancedRecordUtils(
    witherOptions = @WitherOptions(
        witherName = "Having"
    )
)
public record Person(String name, int age)
implements PersonUtils.Having {}
convertToBuilder

Sets the name of the method that converts an instance of the record back into the builder.

Type

String

Default

"with"

Usage: Changing the method name
@AdvancedRecordUtils(
    witherOptions = @WitherOptions(
        convertToBuilder = "toBuilder"
    )
)
public record Person(String name, int age)
implements PersonUtils.All {}

In the above example, the generated method is callable like so

Sample method body
PersonUtils.Builder personFactory = person.toBuilder();

The default looks like so:

Sample method body
PersonUtils.Builder personBuilder = person.with();
withMethodPrefix / withMethodSuffix

Set the prefix and suffix for the with methods.

These are combined with the name of the method on the `Builder, which is based on the component name. This is because the `With internally calls the Builder to perform the requested change.
Setting both of these to blank strings will result in conflicts with the accessor methods on the record.
Type

String

Default

"with" (prefix), "" (suffix)

Usage: Changing the method name
@AdvancedRecordUtils(
    witherOptions = @WitherOptions(
        withMethodPrefix = "",
        withMethodSuffix = "ButDifferent"
    )
)
public record Person(String name, int age)
implements PersonUtils.All {}

In the above example, the generated method is callable like so

Sample method body
Person personC = personA.nameButDifferent("Cloud");

The default looks like so:

Sample method body
Person personC = personA.withName("Cissnei");

4.6. Merger options

These control the names and behaviour for generating the Mergeable interface. These are applied to the mergerOptions = @MergerOptions() sub-setting like so:

Placement example
@AdvancedRecordUtils(
    mergerOptions = @MergerOptions(
        // Options go here
    ),
    merger = true
)
public record Person(String name, int age) {}
mergerName

Sets the name of the Mergeable interface.

Type

String

Default

"Mergeable"

Usage: Changing the name of the interface
@AdvancedRecordUtils(
    mergerOptions = @MergerOptions(
        mergerName = "Combinable"
    ),
    merger = true
)
public record Person(String name, int age)
implements PersonUtils.Combinable {}
mergerMethodName

Sets the name of the method that combines two instances of the record together.

Type

String

Default

"merge"

Usage: Changing the method name
@AdvancedRecordUtils(
    mergerOptions = @MergerOptions(
        mergerMethodName = "combine"
    ),
    merger = true
)
public record Person(String name, int age)
implements PersonUtils.All {}

In the above example, the generated method is callable like so:

Sample method body
Person personC = personA.combine(personB);

The default looks like so:

Sample method body
Person personC = personA.merge(personB);
staticMethodsAddedToUtils

Should a static version of the merge method be placed on the top-level *Utils class?

Type

boolean

Default

false

Usage: Create the static method
@AdvancedRecordUtils(
    mergerOptions = @MergerOptions(
        staticMethodsAddedToUtils = true
    ),
    merger = true
)
public record Person(String name, int age) {}

You can then use it like so:

Sample method body
Person personC = PersonUtils.merge(personA, personB);
Generated code on the top-level *Utils class
public final class PersonUtils implements GeneratedUtil {
    public static final Person merge(
            @Nullable final Person preferred,
            @Nullable final Person other
    ) {
        return _MergerUtils.merge(preferred, other);
    }
}

4.7. Diffable Options

These control the names and behaviour for generating the Diffable interface and related classes. These are applied to the diffOptions = @DiffOptions() sub-setting like so:

Placement example
@AdvancedRecordUtils(
    diffOptions = @DiffOptions(
        // Options go here
    ),
    diffable = true
)
public record Person(String name, int age) {}
differName

Sets the name of the Diffable interface.

Type

String

Default

"Diffable"

Usage: Changing the name of the interface
@AdvancedRecordUtils(
    diffOptions = @DiffOptions(
        differName = "DeltaGeneratable"
    ),
    diffable = true
)
public record Person(String name, int age)
implements PersonUtils.DeltaGeneratable {}
differMethodName

Sets the name of the method that computes the diff.

Type

String

Default

"diff"

Usage: Changing the name of the method
@AdvancedRecordUtils(
    diffOptions = @DiffOptions(
        differMethodName = "delta"
    ),
    diffable = true
)
public record Person(String name, int age)
implements PersonUtils.All {}

In the above example, the generated method is callable like so:

Sample method body
DiffOfPerson diff = personA.delta(personA);

The default looks like so:

Sample method body
DiffOfPerson diff = personA.diff(personA);
diffResultPrefix / diffResultSuffix

Sets the prefix and suffix applied to the record that contains the results of the diff computation.

Type

String

Default

"DiffOf" (prefix), "" (suffix)

Usage: Changing the name of the result type
@AdvancedRecordUtils(
    diffOptions = @DiffOptions(
        diffResultPrefix = "",
        diffResultSuffix = "Changes"
    ),
    diffable = true
)
public record Person(String name, int age)
implements PersonUtils.All {}

In the above example, the result of the diff is stored in an PersonChanges class like so:

Sample method body
PersonChanges diff = personA.diff(personA);

The default looks like so:

Sample method body
DiffOfPerson diff = personA.diff(personA);
originatingElementName / comparedToElementName

Sets the name of the first and second elements being compared.

Type

String

Default

"original" (originating), "updated" (comparedTo)

Usage: Changing the compared element names
@AdvancedRecordUtils(
    diffOptions = @DiffOptions(
        originatingElementName = "parent",
        comparedToElementName = "child"
    ),
    diffable = true
)
public record Person(String name, int age)
implements PersonUtils.All {}

In the above example, obtaining the input items or their fields works like so:

Sample method body
DiffOfPerson diff = personA.diff(personA);
String parentName = diff.parentName();
String childName = diff.childName();

The default looks like so:

Sample method body
DiffOfPerson diff = personA.diff(personA);
String originalName = diff.originalName();
String updatedName = diff.updatedName();
changedCheckPrefix / changedCheckSuffix

Sets the prefix and suffix applied to the component name that reports if that component has changed.

Type

String

Default

"has" (prefix), "Changed" (suffix)

Usage: Changing the changed-check naming
@AdvancedRecordUtils(
    diffOptions = @DiffOptions(
        changedCheckPrefix = "is",
        changedCheckSuffix = "Different"
    ),
    diffable = true
)
public record Person(String name, int age)
implements PersonUtils.All {}

In the above example, to see if a field has changed you can do this:

Sample method body
DiffOfPerson diff = personA.diff(personA);
if (diff.isAgeDifferent()) {
    // Do something
}

The default looks like so:

Sample method body
DiffOfPerson diff = personA.diff(personA);
if (diff.hasAgeChanged()) {
    // Do something
}
changedAnyMethodName

Sets the name of the method that reports if any component is different between the two instances.

Type

String

Default

"hasChanged"

Usage: Changing the aggregate changed-check method
@AdvancedRecordUtils(
    diffOptions = @DiffOptions(
        changedAnyMethodName = "isDifferent"
    ),
    diffable = true
)
public record Person(String name, int age)
implements PersonUtils.All {}

In the above example, the generated method is callable like so

Sample method body
DiffOfPerson diff = personA.diff(personA);
if (diff.isDifferent()) {
    // Do something
}

The default looks like so:

Sample method body
DiffOfPerson diff = personA.diff(personA);
if (diff.hasChanged()) {
    // Do something
}
staticMethodsAddedToUtils

Should a static version of the diff method be placed on the top-level *Utils class?

Type

boolean

Default

false

Usage: Create the static method
@AdvancedRecordUtils(
    diffOptions = @DiffOptions(
        staticMethodsAddedToUtils = true
    ),
    diffable = true
)
public record Person(String name, int age) {}

You can then use it like so:

Sample method body
DiffOfPerson diff = PersonUtils.diff(personA, personB);
Generated code on the top-level *Utils class
public final class PersonUtils implements GeneratedUtil {
    public static final DiffOfPerson diff(final Person original, final Person updated) {
        return new DiffOfRootItem(original, updated);
    }
}

4.8. XML Options

These control the names and behaviour for generating the XML interface and related classes. These are applied to the xmlOptions = @XmlOptions() sub-setting like so:

Placement example
@AdvancedRecordUtils(
    xmlOptions = @XmlOptions(
        // Options go here
    ),
    xmlable = true
)
public record Person(
    @XmlElement(name = "Name")
    String name,
    @XmlElement(name = "Age")
    int age
) {}
xmlName

Sets the name of the XML interface.

Type

String

Default

"XML"

Usage: Changing the name of the interface
@AdvancedRecordUtils(
    xmlOptions = @XmlOptions(
        xmlName = "SerialXml"
    ),
    xmlable = true
)
public record Person(
    @XmlElement(name = "Name")
    String name,
    @XmlElement(name = "Age")
    int age
)
implements PersonUtils.SerialXml {}
continueAddingToXmlMethodName

Sets the name of the method on the interface that writes the object to an XMLStreamWriter.

Type

String

Default

"writeSelfTo"

Usage: Changing the name of the method
@AdvancedRecordUtils(
    xmlOptions = @XmlOptions(
        continueAddingToXmlMethodName = "out"
    ),
    xmlable = true
)
public record Person(
    @XmlElement(name = "Name")
    String name,
    @XmlElement(name = "Age")
    int age
)
implements PersonUtils.All {}

In the above example, the generated method is callable like so:

Sample method body
// You will need to handle the XMLStreamException that can be thrown in this example
personA.out(xmlStreamWriter);

The default looks like so:

Sample method body
// You will need to handle the XMLStreamException that can be thrown in this example
personA.writeSelfTo(xmlStreamWriter);
inferXmlElementName

Determines if @XmlElement annotations should be inferred and, if so, how the inference is done.

Type

NameGeneration

Default

NameGeneration.NONE

Usage
@AdvancedRecordUtils(
    xmlOptions = @XmlOptions(
        inferXmlElementName = .AdvancedRecordUtils.NameGeneration.UPPER_FIRST_LETTER
    ),
    xmlable = true
)
public record Person(
    String name,
    int age
)
implements PersonUtils.All {}

The three values are:

  • NONE - do not infer an @XmlElement annotation

  • MATCH - if there are no other @Xml* annotations, infer an @XmlElement with a name value that matches the component name

  • UPPER_FIRST_LETTER - if there are no other @Xml* annotations, infer an @XmlElement with a name value that matches the component name with the first letter capitalised.

The default behaviour of NONE requires you to specify an annotation. If you do not specify one, the processor will fail compilation and tell you to specify one. If you wish to exclude a component, use @XmlTransient.

The above example is the equivalent of doing:

Person.java
@AdvancedRecordUtils(
    xmlable = true
)
public record Person(
    @XmlElement(name = "Name")
    String name,
    @XmlElement(name = "Age")
    int age
)
implements PersonUtils.All {}

The final value of MATCH would be the equivalent of doing:

Person.java
@AdvancedRecordUtils(
    xmlable = true
)
public record Person(
    @XmlElement(name = "name")
    String name,
    @XmlElement(name = "age")
    int age
)
implements PersonUtils.All {}

Both of these settings "skip" anything that is annotated already with an @Xml* annotation (@XmlTransient, @XmlAttribute, or @XmlElements).