Open-source project
immutables/immutables avatar
immutables/immutables

Immutables: an annotation processor that writes your Java value classes

Java annotation processor to create immutable objects and builders, for records too. Sweep boilerplate code under the rug!

3,575 stars296 forksJavaApache-2.0

At a glance

What is it?
You declare an abstract value type or annotate a record, and the build generates the implementation, the builder and the with-methods. The interesting decisions are all in which of its several styles you pick.
Who is it for?
Immutables is worth adopting when you have a lot of small value types and the boilerplate has become a real maintenance cost, because the generated code is consistent and the record support means you can keep using records rather than retreating to interfaces. Start with the record builder style, since it needs the least ceremony, and only move to the sandwich or classical interface styles once you need the With pattern or a nested builder.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 14 days ago.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • immutables/immutables3,575 ★ · -1
Star history
Repository2026-10-072026-10-23
immutables/immutables3.6k3.6k

-1 Stars in the last 16 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Annotate a record and get a builder back

The shortest path in Immutables is a record. Put `@Value.Builder` on it, and the annotation processor generates a builder class alongside it at compile time:

java
@Value.Builder
record Person(String name, int age, String email) {}

// Use the generated builder
Person person = new PersonBuilder()
    .name("Alice")
    .age(30)
    .email("[email protected]")
    .build();

Note what this is not. The processor is not transforming your record into a different class at runtime, and there is no reflection in the hot path. It writes new Java source into your build output, which means the generated builder is compiled by javac like any hand-written class and shows up in your IDE.

The second example shows the fancier variant, where the record implements a generated `WithPerson` interface so you get copy-with methods, and the builder is configured with the `withUnaryOperator="with*"` style so those copy methods read as `withName` and `withAge`:

java
@Value.Builder
record Person(String name, int age) implements WithPerson {
  // Extend the generated PersonBuilder
  static class Builder extends PersonBuilder {}
}

// Use your custom builder
var person = new Person.Builder()
    .name("Bob")
    .age(18)
    .build();

person = person.withName("Bobby!")
    .withAge(age -> age + 3);

The nested `Builder` class here extends the generated one, so you can add your own methods without giving up the generated ones. The with-methods return a new instance rather than mutating, which is the whole point: state changes produce a new value instead of an in-place edit.

Three styles for declaring an abstract value type

Beyond records, the classical approach declares an abstract type and lets the processor produce the implementation. The minimal form is an annotated interface with accessor methods:

java
@Value.Immutable
interface Book {
  String isbn();
  String title();
  List<String> authors();
}

ImmutableBook book = ImmutableBook.builder()
    .isbn("978-1-56619-909-4")
    .title("The Elements of Style")
    .addAuthors("William Strunk Jr.", "E.B. White.")
    .build();

The naming is mechanical: `Book` becomes `ImmutableBook`, and `ImmutableBook.builder()` exists because you asked for it. The `List<String> authors()` accessor is what produces the `addAuthors` methods, including the varargs form.

The style the README calls sandwich is for when you want the With pattern and a nested builder. You declare an abstract type that extends a generated `With*` interface and nest a `Builder` class extending the generated one:

java
// Define abstract value type using interface, abstract class or annotation
@Value.Immutable
public interface ValueObject extends WithValueObject {
  // WithValueObject is not yet generated, We extend With* to inherit `with*` method signatures
  String name();
  List<Integer> counts();
  Optional<String> description();

  class Builder extends ImmutableValueObject.Builder {}
}

The comment in the source is worth reading carefully, because it documents a forward-reference situation that confuses people: `WithValueObject` does not exist yet when you write it. Extending the `With*` name is how your declaration inherits the `with*` method signatures the processor will generate. Since the `Builder` is a static nested class inside the interface, it inherits and re-exports the public signatures of the generated builder as its own, so you can extend the builder to add methods and still call the generated ones.

The declaration can be an interface, an abstract class or an annotation, which is the flexibility that lets a team keep an existing abstract type and annotate it rather than rewrite it.

The module list is where the surprise lives

Immutables is one Maven artifact that pulls in a lot of separate processors, and the repository tree shows how many independent features exist beyond the value generation itself. Modules in the tree include `criteria`, which generates type-safe query criteria, `encode` for encoders, `serial` for serialization, `mirror` for generating mirrors of third-party types, `gson`, `mongo`, `trees`, `data`, `datatype`, `ordinal`, `func`, `builder` on its own, `metainf`, `annotate`, `bom` for dependency management, plus a large `value/` area with `value-processor` and `value-fixture` alongside `value-annotations`.

Then there is the testing and fixture infrastructure: `generator`, `generator-processor`, `generator-fixture`, `processor-testlib`, `processor-arguments-test`, `testing/` and `value-fixture/`. This is a well-tested annotation processor in the sense that the processor that generates code is itself heavily tested against fixtures.

Two build details show up in the tree as well: `mvnw` and `mvnw.cmd` for a Maven wrapper, `settings.xml` at the root for repository configuration, and `jreleaser.yml` for release automation. There is also an `android-stub/` module and a `.archive/` directory, and the README points to an archived changelog there for releases before the GitHub releases tab.

Topics on the repository are a fair summary of intent: annotation-processor, builder, gson, guava, immutable-collections, immutable-datastructures, immutable-objects, immutables, jackson, java and records. If you only want value types, most of that list is irrelevant to you, which is worth knowing before you add the dependency.

Recent releases are about Jackson 3, JDK versions and edge cases

The 2.12.x line is in maintenance mode rather than adding features, and the release notes show where the project's attention actually goes.

Version 2.12.0, released 2025-12-08, is the twelfth anniversary edition. Its headline item is Jackson 3 support, contributed in pull request 1615, alongside a new and updated website with new guides covering the styles and the modules, now at immutables.github.io which the old immutables.org address redirects to. The same release adds `@immutables.annotations.pick`, described as something that may finally solve the javax versus jakarta annotation problem with a global flag, and `@immutables.guava.suppress`.

Version 2.12.1, released 2026-01-06, upgraded the build and test matrix to include JDK 23 and Java 25 with javac. It also fixed builders for package-private records when visibility was set to PUBLIC, fixed the Staged builder's new `start()` being incompatible with plain old Java objects, and added a `PlainAttribute` as a workaround.

Version 2.12.2, released 2026-05-17, is narrower still: two fixes for finding nested elements on a plain element with the Mongo backend, documentation improvements for records with `implementationNestedInBuilder`, and a fix for TYPE_USE `@Nullable` on concrete array types. The release credits three first-time contributors.

The pattern is a mature project with a wide compatibility surface, where each release absorbs a handful of tricky edge cases from a large community. The repository has 3,575 stars, 295 forks and 460 open issues, is Apache-2.0 licensed, and the last push was on 2026-07-31.

What the README covers and what lives on the website

The README is a code sample document rather than a guide. It shows the record builder, the more elaborate record builder with copy-with methods, the classical immutable interface, and the sandwich style, then points you at immutables.org for the full documentation and at the releases tab for history.

That split is workable because the sample is genuinely the API surface. If you can read four short examples you understand what the library does. What you cannot learn from the README is configuration: which modules to depend on, which style to choose for a given situation, how to control style attributes such as the `withUnaryOperator` setting visible in the second sample, and how the criteria generator, the Mongo integration or the Gson support are meant to be configured.

Two details in the samples hint at the amount of configuration behind them. The sandwich example shows `Optional<String> description()` as an accessor, and the comments in that block go out of their way to explain a forward reference and the inheritance chain for a nested builder. Both are things a reader will hit and want explained properly, and both are explained on the website rather than in the README.

So the practical approach is to copy the closest sample, build it, and then read the guides for the specific style and the specific modules you enabled. The README's four examples are an accurate index of what exists; the website is where the decisions get explained.

Editorial conclusion

Immutables is worth adopting when you have a lot of small value types and the boilerplate has become a real maintenance cost, because the generated code is consistent and the record support means you can keep using records rather than retreating to interfaces. Start with the record builder style, since it needs the least ceremony, and only move to the sandwich or classical interface styles once you need the With pattern or a nested builder. The parts to read before committing are the modules you do not need, which is most of them, and the processor options, because the library does more by default than a first impression suggests. It is Apache-2.0 licensed, at version 2.12.2 as of 2026-05-17 with a last push on 2026-07-31.

Frequently asked questions

What is the difference between Immutables and using Java records directly?

Records give you immutability, equals, hashCode and a canonical constructor for free, but no builder and no copy-with methods. Immutables layers on top: you annotate a record with `@Value.Builder` and the processor generates a builder, and with the right style settings also generates `with*` methods that return a modified copy.

Does the Immutables annotation processor slow down the build?

Annotation processing adds a compilation step, and Immutables ships a lot of processors, so the cost depends on which modules you enable. The repository includes a `processor-testlib` module and fixture directories, which is where the processor's own behaviour is tested. Enabling only the value processor and leaving criteria, Mongo, Gson and the rest off keeps the added work small.

What is the sandwich style in Immutables?

It is one of the declaration styles, where your abstract type extends a generated `With*` interface to inherit `with*` copy-method signatures, and a static nested `Builder` class extends the generated builder to re-export its methods as its own. It gives you both copy-with methods and a place to add custom builder methods without losing the generated ones.

Which Immutables version supports Jackson 3?

Version 2.12.0, released on 2025-12-08, is the release that added Jackson 3 support, contributed in pull request 1615. That same release also added `@immutables.annotations.pick`, which the notes describe as a possible global fix for the javax versus jakarta annotation split.

Official sources

  1. immutables/immutables on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/immutables-immutables.svg)](https://hysenlabs.com/projects/immutables-immutables)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/immutables-immutables.svg)](https://hysenlabs.com/projects/immutables-immutables)

Was this useful?