Comprehensive Java annotation processing framework for generating immutable value objects, marshalers, repositories, and custom code generators with extensive integration support.
—
Annotations for customizing individual attributes including default values, lazy computation, validation, and parameter ordering. These annotations provide fine-grained control over how each attribute behaves in generated immutable classes.
Provide default values for optional attributes in builders.
/**
* Marks a method as providing a default value for an attribute.
* The method body will be used as the default value in builders
* when the attribute is not explicitly set.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
@interface Value.Default {}Usage Example:
import org.immutables.value.Value;
import java.time.Duration;
@Value.Immutable
public interface ServerConfig {
String host();
@Value.Default
default int port() {
return 8080;
}
@Value.Default
default boolean ssl() {
return false;
}
@Value.Default
default Duration timeout() {
return Duration.ofSeconds(30);
}
}
// Usage - port, ssl, and timeout use defaults
ServerConfig config = ImmutableServerConfig.builder()
.host("localhost")
.build();Computed attributes that are stored in fields for performance.
/**
* Marks an attribute as derived/computed. The method implementation
* provides the computation logic, and the result is cached in a field
* for performance.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
@interface Value.Derived {}Usage Example:
import org.immutables.value.Value;
import java.time.LocalDate;
import java.time.Period;
@Value.Immutable
public interface Person {
String firstName();
String lastName();
LocalDate birthDate();
@Value.Derived
default String fullName() {
return firstName() + " " + lastName();
}
@Value.Derived
default int age() {
return Period.between(birthDate(), LocalDate.now()).getYears();
}
}
// Derived attributes are computed once and cached
Person person = ImmutablePerson.builder()
.firstName("Alice")
.lastName("Smith")
.birthDate(LocalDate.of(1990, 5, 15))
.build();
String name = person.fullName(); // Computed once, cached
int age = person.age(); // Computed once, cachedThread-safe lazy computed attributes for expensive operations.
/**
* Marks an attribute as lazily computed. The computation is deferred
* until first access and is thread-safe. Use for expensive computations
* that may not always be needed.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
@interface Value.Lazy {}Usage Example:
import org.immutables.value.Value;
import java.util.List;
@Value.Immutable
public interface DataSet {
List<Double> values();
@Value.Lazy
default double mean() {
return values().stream()
.mapToDouble(Double::doubleValue)
.average()
.orElse(0.0);
}
@Value.Lazy
default double standardDeviation() {
double mean = mean();
return Math.sqrt(
values().stream()
.mapToDouble(v -> Math.pow(v - mean, 2))
.average()
.orElse(0.0)
);
}
}
// Expensive computations are deferred until needed
DataSet data = ImmutableDataSet.builder()
.addValues(1.0, 2.0, 3.0, 4.0, 5.0)
.build();
// Only computed when first accessed, then cached
double mean = data.mean();
double stdDev = data.standardDeviation();Control parameter ordering and inclusion in generated constructors.
/**
* Marks an accessor method as a constructor parameter.
* Controls the order of parameters in generated constructors
* and factory methods.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
@interface Value.Parameter {
/** Order of parameter in constructor (lower values come first) */
int order() default 0;
}Usage Example:
@Value.Immutable
public interface Point3D {
@Value.Parameter(order = 1)
double x();
@Value.Parameter(order = 2)
double y();
@Value.Parameter(order = 3)
double z();
// Not a constructor parameter
@Value.Derived
default double magnitude() {
return Math.sqrt(x() * x() + y() * y() + z() * z());
}
}
// Generated constructor respects parameter ordering
Point3D point = ImmutablePoint3D.of(1.0, 2.0, 3.0); // x, y, z orderValidation method invocation for instance validation.
/**
* Marks a method as a validation method. The method will be called
* during instance construction to validate the object state.
* Should throw an exception if validation fails.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
@interface Value.Check {}Usage Example:
@Value.Immutable
public interface Rectangle {
double width();
double height();
@Value.Check
default void validate() {
if (width() <= 0) {
throw new IllegalArgumentException("Width must be positive");
}
if (height() <= 0) {
throw new IllegalArgumentException("Height must be positive");
}
}
@Value.Derived
default double area() {
return width() * height();
}
}
// Validation is automatically called during construction
try {
Rectangle rect = ImmutableRectangle.builder()
.width(-1.0) // Invalid!
.height(5.0)
.build(); // Throws IllegalArgumentException
} catch (IllegalArgumentException e) {
// Handle validation error
}Exclude attributes from equals, hashCode, and toString methods.
/**
* Marks an attribute as auxiliary. Auxiliary attributes are excluded
* from equals(), hashCode(), and toString() methods but are still
* part of the immutable object.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
@interface Value.Auxiliary {}Usage Example:
@Value.Immutable
public interface CacheEntry {
String key();
String value();
@Value.Auxiliary // Not part of equality/hash
Instant createdAt();
@Value.Auxiliary // Not part of equality/hash
int accessCount();
}
// Two entries with same key/value are equal regardless of auxiliary fields
CacheEntry entry1 = ImmutableCacheEntry.builder()
.key("user:123")
.value("Alice")
.createdAt(Instant.now())
.accessCount(5)
.build();
CacheEntry entry2 = ImmutableCacheEntry.builder()
.key("user:123")
.value("Alice")
.createdAt(Instant.now().minusSeconds(10)) // Different time
.accessCount(2) // Different count
.build();
// Still equal because auxiliary fields are ignored
assert entry1.equals(entry2); // true
assert entry1.hashCode() == entry2.hashCode(); // trueMultiple attribute annotations can be combined for complex behavior:
@Value.Immutable
public interface ComplexType {
String name();
@Value.Default
@Value.Parameter(order = 1)
default String category() {
return "default";
}
@Value.Lazy
@Value.Auxiliary
default String expensiveComputation() {
// Expensive operation not included in equals/hash
return performExpensiveCalculation();
}
@Value.Derived
@Value.Check
default String normalizedName() {
String normalized = name().toLowerCase().trim();
if (normalized.isEmpty()) {
throw new IllegalArgumentException("Name cannot be empty");
}
return normalized;
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-immutables--immutables