or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

builder-pattern.mdconstructors.mddata-classes.mdexperimental.mdimmutable-patterns.mdindex.mdlogging.mdobject-methods.mdproperty-access.mdtype-inference.mdutilities.md
tile.json

immutable-patterns.mddocs/

Immutable Patterns

Generate immutable "wither" methods that create copies of objects with modified field values. The @With annotation provides functional-style object copying while maintaining immutability.

Capabilities

@With Annotation

Generates wither methods that create a copy of the object with one field changed, enabling immutable object updates.

/**
 * Put on any field to make lombok build a 'with' - a withX method which produces a clone of this object 
 * (except for 1 field which gets a new value).
 * 
 * This annotation can also be applied to a class, in which case it'll be as if all non-static fields 
 * that don't already have a @With annotation have the annotation.
 */
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface With {
    /**
     * If you want your with method to be non-public, you can specify an alternate access level here.
     * 
     * @return The method will be generated with this access modifier.
     */
    AccessLevel value() default AccessLevel.PUBLIC;
    
    /**
     * Any annotations listed here are put on the generated method.
     * The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).
     * up to JDK7:
     *  @With(onMethod=@__({@AnnotationsGoHere}))
     * from JDK8:
     *  @With(onMethod_={@AnnotationsGohere}) // note the underscore after onMethod.
     * 
     * @return List of annotations to apply to the generated method.
     */
    AnyAnnotation[] onMethod() default {};
    
    /**
     * Any annotations listed here are put on the generated method's parameter.
     * The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).
     * up to JDK7:
     *  @With(onParam=@__({@AnnotationsGoHere}))
     * from JDK8:
     *  @With(onParam_={@AnnotationsGohere}) // note the underscore after onParam.
     * 
     * @return List of annotations to apply to the generated parameter in the method.
     */
    AnyAnnotation[] onParam() default {};
}

Usage Examples:

import lombok.With;
import lombok.AllArgsConstructor;

@AllArgsConstructor
public class Person {
    @With private final String name;
    @With private final int age;
    private final String email;
}

// Generated methods available:
// - Person withName(String name)
// - Person withAge(int age)

// Usage
Person original = new Person("John Doe", 30, "john@example.com");
Person updated = original.withAge(31);
Person renamed = original.withName("Jane Doe");

System.out.println(original.getAge()); // 30
System.out.println(updated.getAge());  // 31 
System.out.println(renamed.getName()); // Jane Doe

Class-Level Application:

import lombok.With;
import lombok.Value;

@Value
@With
public class ImmutablePoint {
    double x;
    double y;
    String label;
}

// Generated methods available:
// - ImmutablePoint withX(double x)
// - ImmutablePoint withY(double y) 
// - ImmutablePoint withLabel(String label)

// Usage
ImmutablePoint origin = new ImmutablePoint(0.0, 0.0, "origin");
ImmutablePoint moved = origin.withX(10.0).withY(5.0);
ImmutablePoint relabeled = moved.withLabel("moved point");

Advanced Usage

Combining with @Value

import lombok.Value;
import lombok.With;

@Value
@With
public class Configuration {
    String host;
    int port;
    boolean enableSsl;
    String username;
    
    // All fields get wither methods
}

// Usage
Configuration config = new Configuration("localhost", 8080, false, "admin");
Configuration sslConfig = config.withEnableSsl(true).withPort(8443);
Configuration prodConfig = sslConfig.withHost("prod.server.com");

Access Level Control

import lombok.With;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;

@AllArgsConstructor
public class Account {
    @With(AccessLevel.PACKAGE) private final String accountId;
    @With private final String name;
    @With(AccessLevel.PRIVATE) private final double balance;
    
    // Package-private: Account withAccountId(String accountId)
    // Public: Account withName(String name)  
    // Private: Account withBalance(double balance)
    
    public Account deposit(double amount) {
        return withBalance(balance + amount);
    }
    
    public Account withdraw(double amount) {
        return withBalance(balance - amount);
    }
}

Null Safety Integration

import lombok.With;
import lombok.NonNull;
import lombok.AllArgsConstructor;

@AllArgsConstructor
public class User {
    @With @NonNull private final String username;
    @With private final String email;
    @With private final Integer age;
}

// Generated method includes null check:
// public User withUsername(@NonNull String username) {
//     if (username == null) throw new NullPointerException("username is marked @NonNull but is null");
//     return this.username == username ? this : new User(username, this.email, this.age);
// }

Generated Code Behavior

Method Generation Pattern

For each @With annotated field, lombok generates a method with the pattern:

  • Method name: with + capitalized field name
  • Parameter: Same type as the field
  • Return type: Same type as the containing class
  • Behavior: Returns this if value equals current field value, otherwise creates new instance

Performance Optimization

The generated wither methods include an optimization that returns the same instance if the new value equals the current value:

public Person withAge(int age) {
    return this.age == age ? this : new Person(this.name, age, this.email);
}

Constructor Requirements

The @With annotation requires that a constructor exists that takes all fields as parameters in the same order they're declared in the class. This is automatically satisfied when using:

  • @AllArgsConstructor
  • @Value
  • @Data (if no explicit constructors exist)

Configuration

The @With annotation respects lombok configuration settings:

  • lombok.with.flagUsage: Control usage warnings
  • lombok.accessors.fluent: Affects generated method names when fluent accessors are enabled
  • lombok.accessors.chain: Affects return type when chaining is enabled