or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

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

immutable-patterns.mddocs/

0

# Immutable Patterns

1

2

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

3

4

## Capabilities

5

6

### @With Annotation

7

8

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

9

10

```java { .api }

11

/**

12

* Put on any field to make lombok build a 'with' - a withX method which produces a clone of this object

13

* (except for 1 field which gets a new value).

14

*

15

* This annotation can also be applied to a class, in which case it'll be as if all non-static fields

16

* that don't already have a @With annotation have the annotation.

17

*/

18

@Target({ElementType.FIELD, ElementType.TYPE})

19

@Retention(RetentionPolicy.SOURCE)

20

public @interface With {

21

/**

22

* If you want your with method to be non-public, you can specify an alternate access level here.

23

*

24

* @return The method will be generated with this access modifier.

25

*/

26

AccessLevel value() default AccessLevel.PUBLIC;

27

28

/**

29

* Any annotations listed here are put on the generated method.

30

* The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).

31

* up to JDK7:

32

* @With(onMethod=@__({@AnnotationsGoHere}))

33

* from JDK8:

34

* @With(onMethod_={@AnnotationsGohere}) // note the underscore after onMethod.

35

*

36

* @return List of annotations to apply to the generated method.

37

*/

38

AnyAnnotation[] onMethod() default {};

39

40

/**

41

* Any annotations listed here are put on the generated method's parameter.

42

* The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).

43

* up to JDK7:

44

* @With(onParam=@__({@AnnotationsGoHere}))

45

* from JDK8:

46

* @With(onParam_={@AnnotationsGohere}) // note the underscore after onParam.

47

*

48

* @return List of annotations to apply to the generated parameter in the method.

49

*/

50

AnyAnnotation[] onParam() default {};

51

}

52

```

53

54

**Usage Examples:**

55

56

```java

57

import lombok.With;

58

import lombok.AllArgsConstructor;

59

60

@AllArgsConstructor

61

public class Person {

62

@With private final String name;

63

@With private final int age;

64

private final String email;

65

}

66

67

// Generated methods available:

68

// - Person withName(String name)

69

// - Person withAge(int age)

70

71

// Usage

72

Person original = new Person("John Doe", 30, "john@example.com");

73

Person updated = original.withAge(31);

74

Person renamed = original.withName("Jane Doe");

75

76

System.out.println(original.getAge()); // 30

77

System.out.println(updated.getAge()); // 31

78

System.out.println(renamed.getName()); // Jane Doe

79

```

80

81

**Class-Level Application:**

82

83

```java

84

import lombok.With;

85

import lombok.Value;

86

87

@Value

88

@With

89

public class ImmutablePoint {

90

double x;

91

double y;

92

String label;

93

}

94

95

// Generated methods available:

96

// - ImmutablePoint withX(double x)

97

// - ImmutablePoint withY(double y)

98

// - ImmutablePoint withLabel(String label)

99

100

// Usage

101

ImmutablePoint origin = new ImmutablePoint(0.0, 0.0, "origin");

102

ImmutablePoint moved = origin.withX(10.0).withY(5.0);

103

ImmutablePoint relabeled = moved.withLabel("moved point");

104

```

105

106

## Advanced Usage

107

108

### Combining with @Value

109

110

```java

111

import lombok.Value;

112

import lombok.With;

113

114

@Value

115

@With

116

public class Configuration {

117

String host;

118

int port;

119

boolean enableSsl;

120

String username;

121

122

// All fields get wither methods

123

}

124

125

// Usage

126

Configuration config = new Configuration("localhost", 8080, false, "admin");

127

Configuration sslConfig = config.withEnableSsl(true).withPort(8443);

128

Configuration prodConfig = sslConfig.withHost("prod.server.com");

129

```

130

131

### Access Level Control

132

133

```java

134

import lombok.With;

135

import lombok.AccessLevel;

136

import lombok.AllArgsConstructor;

137

138

@AllArgsConstructor

139

public class Account {

140

@With(AccessLevel.PACKAGE) private final String accountId;

141

@With private final String name;

142

@With(AccessLevel.PRIVATE) private final double balance;

143

144

// Package-private: Account withAccountId(String accountId)

145

// Public: Account withName(String name)

146

// Private: Account withBalance(double balance)

147

148

public Account deposit(double amount) {

149

return withBalance(balance + amount);

150

}

151

152

public Account withdraw(double amount) {

153

return withBalance(balance - amount);

154

}

155

}

156

```

157

158

### Null Safety Integration

159

160

```java

161

import lombok.With;

162

import lombok.NonNull;

163

import lombok.AllArgsConstructor;

164

165

@AllArgsConstructor

166

public class User {

167

@With @NonNull private final String username;

168

@With private final String email;

169

@With private final Integer age;

170

}

171

172

// Generated method includes null check:

173

// public User withUsername(@NonNull String username) {

174

// if (username == null) throw new NullPointerException("username is marked @NonNull but is null");

175

// return this.username == username ? this : new User(username, this.email, this.age);

176

// }

177

```

178

179

## Generated Code Behavior

180

181

### Method Generation Pattern

182

183

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

184

- **Method name**: `with` + capitalized field name

185

- **Parameter**: Same type as the field

186

- **Return type**: Same type as the containing class

187

- **Behavior**: Returns `this` if value equals current field value, otherwise creates new instance

188

189

### Performance Optimization

190

191

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

192

193

```java

194

public Person withAge(int age) {

195

return this.age == age ? this : new Person(this.name, age, this.email);

196

}

197

```

198

199

### Constructor Requirements

200

201

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:

202

- `@AllArgsConstructor`

203

- `@Value`

204

- `@Data` (if no explicit constructors exist)

205

206

## Configuration

207

208

The `@With` annotation respects lombok configuration settings:

209

210

- `lombok.with.flagUsage`: Control usage warnings

211

- `lombok.accessors.fluent`: Affects generated method names when fluent accessors are enabled

212

- `lombok.accessors.chain`: Affects return type when chaining is enabled