CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-immutables--value

Compile-time annotation processor for generating immutable value objects with builder patterns and compile-time validation.

Pending
Overview
Eval results
Files

attributes.mddocs/

Attribute Customization

Advanced attribute behavior annotations for flexible object modeling including default values, lazy computation, derived values, parameter configuration, and auxiliary attributes.

Capabilities

Default Values

Specify default values for attributes that are optional during construction.

/**
 * Annotates accessor that should be turned into settable generated attribute
 * that is non-mandatory to set via builder. Default value obtained by calling
 * the annotated method.
 */
@interface Value.Default { }

Usage Examples:

@Value.Immutable
public interface Server {
    String host();
    
    @Value.Default
    default int port() { return 8080; }
    
    @Value.Default
    default boolean ssl() { return false; }
    
    @Value.Default
    default List<String> tags() { return List.of(); }
}

// Builder usage - default values are optional
Server server1 = ImmutableServer.builder()
    .host("localhost")
    .build(); // port=8080, ssl=false, tags=[]

Server server2 = ImmutableServer.builder()
    .host("example.com")
    .port(443)
    .ssl(true)
    .addTags("production", "web")
    .build();

Lazy Attributes

Thread-safe lazy computation of expensive attribute values.

/**
 * Lazy attributes cannot be set during building but are computed lazily
 * once and only once in a thread-safe manner. Computed from other attributes.
 * Always act as auxiliary (excluded from equals, hashCode, toString).
 */
@interface Value.Lazy { }

Usage Examples:

@Value.Immutable
public interface Order {
    List<Item> items();
    
    @Value.Lazy
    default BigDecimal totalCost() {
        return items().stream()
            .map(item -> item.price().multiply(BigDecimal.valueOf(item.quantity())))
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    
    @Value.Lazy
    default String summary() {
        return String.format("Order with %d items, total: $%.2f", 
            items().size(), totalCost());
    }
}

// Usage - lazy values computed on first access
Order order = ImmutableOrder.builder()
    .addItems(item1, item2, item3)
    .build();

// First access triggers computation and caches result
BigDecimal cost = order.totalCost(); // Computes and caches
BigDecimal sameCost = order.totalCost(); // Returns cached value

Derived Attributes

Eagerly computed attributes that are calculated from other attributes during construction.

/**
 * Derived attributes cannot be set during building but are eagerly computed
 * from other attributes and stored in field. Should be applied to non-abstract
 * method that serves as the attribute value initializer.
 */
@interface Value.Derived { }

Usage Examples:

@Value.Immutable
public interface Rectangle {
    double width();
    double height();
    
    @Value.Derived
    default double area() {
        return width() * height();
    }
    
    @Value.Derived
    default double perimeter() {
        return 2 * (width() + height());
    }
    
    @Value.Derived
    default String dimensions() {
        return width() + "x" + height();
    }
}

// Derived values are computed during construction
Rectangle rect = ImmutableRectangle.builder()
    .width(10.0)
    .height(5.0)
    .build();

// Values are already computed and stored
double area = rect.area(); // Returns pre-computed 50.0
String dims = rect.dimensions(); // Returns pre-computed "10.0x5.0"

Parameter Configuration

Control which attributes become constructor parameters and their ordering.

/**
 * Mark abstract accessor method to be included as constructor parameter.
 * For constructable objects, all non-default and non-derived attributes
 * should be annotated with @Value.Parameter.
 */
@interface Value.Parameter {
    /**
     * Specify order of constructor argument. Defaults to -1 (unspecified).
     * Arguments are sorted ascending by this order value.
     */
    int order() default -1;
    
    /**
     * Whether to include as parameter. Set false to cancel out parameter
     * (useful with Style.allParameters flag).
     */
    boolean value() default true;
}

Usage Examples:

@Value.Immutable
public interface Person {
    @Value.Parameter(order = 1)
    String firstName();
    
    @Value.Parameter(order = 2)
    String lastName();
    
    @Value.Parameter(order = 3)
    int age();
    
    @Value.Default
    default String email() { return ""; }
}

// Generated constructor: ImmutablePerson.of(firstName, lastName, age)
Person person = ImmutablePerson.of("John", "Doe", 30);

// Builder still available
Person person2 = ImmutablePerson.builder()
    .firstName("Jane")
    .lastName("Smith")
    .age(25)
    .email("jane@example.com")
    .build();

// Exclude from parameters while using allParameters style
@Value.Immutable
@Value.Style(allParameters = true)
public interface Coordinate {
    double x();
    double y();
    
    @Value.Parameter(false) // Exclude from constructor
    @Value.Default
    default String label() { return ""; }
}

// Constructor: ImmutableCoordinate.of(x, y) - label excluded

Auxiliary Attributes

Attributes that are stored and accessible but excluded from equals, hashCode, and toString methods.

/**
 * Annotate attribute as auxiliary - will be stored and accessible but
 * excluded from generated equals(), hashCode() and toString() methods.
 * Lazy attributes are always auxiliary.
 */
@interface Value.Auxiliary { }

Usage Examples:

@Value.Immutable
public interface CacheEntry {
    String key();
    Object value();
    
    @Value.Auxiliary
    long createdTimestamp();
    
    @Value.Auxiliary
    int accessCount();
    
    @Value.Auxiliary
    @Value.Default
    default String debugInfo() { return ""; }
}

// Auxiliary attributes don't affect equality
CacheEntry entry1 = ImmutableCacheEntry.builder()
    .key("user:123")
    .value(userData)
    .createdTimestamp(System.currentTimeMillis())
    .accessCount(0)
    .build();

CacheEntry entry2 = ImmutableCacheEntry.builder()
    .key("user:123")
    .value(userData)
    .createdTimestamp(System.currentTimeMillis() + 1000) // Different timestamp
    .accessCount(5) // Different access count
    .build();

// These are considered equal (auxiliary attributes ignored)
assert entry1.equals(entry2); // true
assert entry1.hashCode() == entry2.hashCode(); // true

// But auxiliary values are still accessible
long timestamp = entry1.createdTimestamp();
int count = entry1.accessCount();

Attribute Validation

Mark methods as non-attributes to prevent them from becoming generated attributes.

/**
 * Mark some abstract no-argument methods in supertypes as regular,
 * non-attribute methods. Prevents annotation processor from generating
 * field, accessor, and builder initializer.
 */
@interface Value.NonAttribute { }

Usage Examples:

// Base interface with methods that shouldn't be attributes
public interface Validatable {
    @Value.NonAttribute
    default boolean isValid() {
        // Custom validation logic
        return true;
    }
    
    @Value.NonAttribute
    default List<String> validate() {
        // Return validation errors
        return List.of();
    }
}

@Value.Immutable
public interface User extends Validatable {
    String name();
    String email();
    
    // Override validation methods
    @Override
    default boolean isValid() {
        return !name().isEmpty() && email().contains("@");
    }
    
    @Override
    default List<String> validate() {
        List<String> errors = new ArrayList<>();
        if (name().isEmpty()) errors.add("Name is required");
        if (!email().contains("@")) errors.add("Invalid email format");
        return errors;
    }
}

// isValid() and validate() are regular methods, not attributes
User user = ImmutableUser.builder()
    .name("John")
    .email("john@example.com")
    .build();

boolean valid = user.isValid(); // Method call, not attribute access
List<String> errors = user.validate(); // Method call, not attribute access

Install with Tessl CLI

npx tessl i tessl/maven-org-immutables--value

docs

advanced-features.md

attributes.md

core-immutable.md

index.md

style-configuration.md

validation.md

tile.json