Immutables

Style customization

Overview

In addition to feature annotations (which are used for specific features), you can customize what and how code will be generated for immutable values. The “what” is defined by using attributes of the @org.immutables.value.Value.Immutable annotation. The “how” is defined by using styles defined by the @org.immutables.value.Value.Style annotation. Custom styles are definitely more advanced functionality and the annotation processor cannot guarantee that all possible combinations of customizations will work correctly.

Define style

@Value.Style annotation has a number of attributes to customize generated the APIs and implementations. See the JavaDoc for Value.Style

In nutshell, using styles you can:

Apply style

A Style can be attached to:

A @Value.Style as inline style will win over meta-annotation style.

It is recommended to create one or more style meta-annotations for your projects. Doing this will result in a lot less clutter, and easier maintenance and upgrades.

import org.immutables.value.Value;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Retention;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.PACKAGE, ElementType.TYPE})
@Retention(RetentionPolicy.CLASS) // Make it class retention for incremental compilation
@Value.Style(
    get = {"is*", "get*"}, // Detect 'get' and 'is' prefixes in accessor methods
    init = "set*", // Builder initialization methods will have 'set' prefix
    typeAbstract = {"Abstract*"}, // 'Abstract' prefix will be detected and trimmed
    typeImmutable = "*", // No prefix or suffix for generated immutable type
    builder = "new", // construct builder using 'new' instead of factory method
    build = "create", // rename 'build' method on builder to 'create'
    visibility = ImplementationVisibility.PUBLIC, // Generated class will be always public
    defaults = @Value.Immutable(copy = false)) // Disable copy methods by default
public @interface MyStyle {}
...


@Value.Immutable // if no attributes are specified, then defaults will be used
@MyStyle // This annotation could be also placed on package
interface AbstractItem {
  int getId();
  boolean isEnabled();
}
...

Item item = new Item.Builder()
  .setId(1)
  .setEnabled(true)
  .create();

This way you can match the generated code style to your conventions and preferences!

The simplest way to apply styles in manner as close to globally as possible is to annotate the top-level package of your project or module.

// com/mycompany/project/package-info.java
@MyStyle
package com.mycompany.project;

Things to be aware of

Depluralization

Style.depluralize style attribute enables automatic depluralization of attribute names for collection and map attributes used for generating derived method names, such as add* and put*. In order to enable depluralization specify depluralize = true: this will trim trailing “s” suffixes if present to create singular form (“ies” to “y” suffix conversion is also supported).

Exceptions are provided using Style.depluralizeDictionary array of "singular:plural" pairs as alternative to mechanical "*s" depluralization.

@Value.Style(
  depluralize = true, // enable feature
  depluralizeDictionary = {"person:people", "foot:feet"}) // specifying dictionary of exceptions

When given the dictionary defined as {"person:people", "foot:feet"} then examples for add* method in builder would be:

Dictionary-based depluralization is based on the assumption that simple s trimming will cover most cases, while exceptions, if provided, are likely to be ubiquitous in a problem domain being modelled by value objects. As a reminder, you don’t have to annotate every single value class with bulky style definitions, rather annotate some top-level package or use style as meta annotation (See Apply style)

Other customizations

Simpler imports

This example can barely be called a customization. Let it just be a reminder that you can use annotations with simple names rather than qualified with umbrella annotations like @Value:

import org.immutables.value.Value.Immutable;
import org.immutables.value.Value.Parameter;

@Immutable interface Value {
  @Parameter int getFirst();
  @Parameter String getSecond();
}

“is” prefix and custom getters

As “strange” as it sounds, but out-of-the-box only get-prefixed or no-prefix accessors are supported. The isEmpty() accessor will be considered as isEmpty attribute, not as one called empty. Yep, Immutables is not a JavaBean-compliant toolkit and don’t expect it to be. However, styles allows you to specify arbitrary prefixes (or even suffixes) to be recognized as part of attribute accessors. Most folks that want to use familiar get and is prefixes simply use @Value.Style(get = {"get*", "is*"}) configured for a parent-package or as a meta-annotation.

@Value.Immutable
@Value.Style(get = {"get*", "is*", "*Whatever"}, init = "set*")
interface Val {
  int getProp();
  boolean isEmpty();
  String fooWhatever();

  static void demo() {
    Val v = ImmutableVal.builder()
      .setProp(1)
      .setEmpty(true)
      .setFoo("whatever")
      .build();

    v.toString();// Val{prop=1, empty=true, foo=whatever}
  }
}

Enclosing type

When modelling messages and documents, we usually want to have a lot of small value classes in one file. In Java this naturally accomplished by nesting those classes under an umbrella top-level class. Of course, it is possible to generate immutable subclasses for nested static inner classes or interfaces.

@Value.Enclosing annotation can be used on a top-level class to provide namespacing for implementation classes generated out of nested abstract value types. This can be used as a matter of preference or to avoid name clashes of immutable implementation classes which would otherwise be generated as top-level classes in the same package.

By default, namespaced implementation classes have simple names without prefixes, an can be star-imported for clutter-free usage.

@Value.Enclosing
class GraphPrimitives {
  @Value.Immutable
  interface Vertex {}
  @Value.Immutable
  static class Edge {}
}
...
import ...ImmutableGraphPrimitives.*;
...
Edge.builder().build();
Vertex.builder().build();

There are number of styling options available to customize naming of generated top-level and nested classes. It worth to note that style annotation should be placed on a top-level enclosing class or on a package, because style of the enclosing class and nested value objects should be the same.

Note: prior to 2.0, @Value.Enclosing was named @Value.Nested

Note: as of 2.1 we would not advertise the use of @Value.Enclosing. It is not deprecated and may be useful as namespacing tool, but still this should be considered as a niche solution, not the one that you should use “by default”. Just nesting value types and generating top-level immutable classes in the same package works fine in many cases.

Custom immutable annotation

What if you want nice single annotation to express immutable object along with certain style? Or better set of annotation with predefined styles? Let’s define two such annotations as a contrived example.

package org.example.annotation;

import org.immutables.value.Value;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

/**
 * Tupled annotation will be used to generate simple tuples in reverse-style,
 * having construction methods of all annotations.
 */
@Value.Style( // Tupled annotation will serve as both immutable and meta-annotated style annotation
    typeAbstract = "*Def",
    typeImmutable = "*",
    allParameters = true, // all attributes will become constructor parameters
                          // as if they are annotated with @Value.Parameter
    visibility = Value.Style.ImplementationVisibility.PUBLIC, // Generated class will be always public
    defaults = @Value.Immutable(builder = false)) // Disable copy methods and builder
public @interface Tupled {}
...

/**
 * Builded annotation will generate builder which produces private implementations
 * of abstract value type.
 */
@Target(ElementType.TYPE)
@Value.Style(
    typeBuilder = "BuilderFor_*",
    defaultAsDefault = true, // java 8 default methods will be automatically turned into @Value.Default
    visibility = Value.Style.ImplementationVisibility.PRIVATE,
    builderVisibility = Value.Style.BuilderVisibility.PACKAGE) // We will extend builder to make it public
public @interface Builded {}

But those definitions along is not enough. Create text file as classpath resource having path /META-INF/annotations/org.immutables.value.immutable and put one or more lines with fully qualified names of extension annotations:

org.example.annotation.Tupled
org.example.annotation.Builded

Ok, now compile annotations and put the above file as a separate jar and then put it on the same classpath/scope as the annotation processor during build along with regular compilation(only) classpath. The annotation jar is not needed at runtime.

Then we can use this annotation module as compile/annotation-processing dependency. Using Maven just put it in provided scope.

package org.example.models;

import org.example.annotation.Tupled;
import org.example.annotation.Builded;

// Look, custom annotation instead of @Value.Immutable
// and the style is also attached!
@Tupled interface RgbDef {
  double red();
  double green();
  double blue();
}
// ...
Rgb color = Rgb.of(0.4, 0.3, 1.0);

// Custom annotation for builder with private immutable implementation.
@Builded public interface Record {
  long id();
  String name();

  default String notes() { // Works as default attribute!
    return "";
  }

  // Then we extend package-private builder with public nested builder and expose all
  // public methods as methods of Record.Builder.
  class Builder extends BuilderFor_Record {}
}
// ...
Record record = new Record.Builder()
    .id(123L)
    .name("Named Record")
    .notes("Oh, nothing interesting, surely!")
    .build();