1# AutoValue and Java Records 2 3 4Starting with Java 16, 5[records](https://docs.oracle.com/en/java/javase/19/language/records.html) are a 6standard feature of the language. If records are available to you, is there any 7reason to use AutoValue? 8 9## <a id="summary"></a>The short answer 10 11Generally, **use records** when you can. They have a very concise and readable 12syntax, they produce less code, and they don't need any special configuration or 13dependency. They are obviously a better choice when your class is just an 14aggregation of values, for example to allow a method to return multiple values 15or to combine values into a map key. 16 17(This was by design: the AutoValue authors were part of the 18[Project Amber](https://openjdk.org/projects/amber/) working group, where our 19goal was to make the records feature the best AutoValue replacement it could 20be.) 21 22If you have existing code that has AutoValue classes, you might want to migrate 23some or all of those classes to be records instead. In this document we will 24explain how to do this, and in what cases you might prefer not to. 25 26## <a id="notyet"></a>Can't use Java records yet? 27 28If you're creating new AutoValue classes for Java 15 or earlier, **follow this 29advice** to make sure your future conversion to records will be straightforward: 30 31* Extend `Object` only (implementing interfaces is fine). 32* Don't use JavaBeans-style prefixes: use `abstract int bar()`, not `abstract 33 int getBar()`. 34* Don't declare any non-static fields of your own. 35* Give the factory method and accessors the same visibility level as the 36 class. 37* Avoid using [extensions](extensions.md). 38 39Adopting AutoValue at this time is still a good idea! There is no better way to 40make sure your code is as ready as possible to migrate to records later. 41 42## <a id="whynot"></a>Reasons to stick with AutoValue 43 44While records are usually better, there are some AutoValue features that have no 45simple equivalent with records. So you might prefer not to try migrating 46AutoValue classes that use those features, and you might even sometimes make new 47AutoValue classes even if records are available to you. 48 49### Extensions 50 51AutoValue has [extensions](extensions.md). Some are built in, like the 52[`@Memoized`](https://javadoc.io/static/com.google.auto.value/auto-value-annotations/1.10/com/google/auto/value/extension/memoized/Memoized.html), 53[`@ToPrettyString`](https://javadoc.io/static/com.google.auto.value/auto-value-annotations/1.10/com/google/auto/value/extension/toprettystring/ToPrettyString.html), 54and 55[`@SerializableAutoValue`](https://javadoc.io/static/com.google.auto.value/auto-value-annotations/1.10/com/google/auto/value/extension/serializable/SerializableAutoValue.html) 56extensions. Most extensions will have no real equivalent with records. 57 58### <a id="staticfactory"></a> Keeping the static factory method 59 60AutoValue has very few API-visible "quirks", but one is that it forces you to 61use a static factory method as your class's creation API. A record can have this 62too, but it can't prevent its constructor from *also* being visible, and 63exposing two ways to do the same thing can be dangerous. 64 65We think most users will be happy to switch to constructors and drop the factory 66methods, but you might want to keep a factory method in some records. Perhaps 67for compatibility reasons, or because you are normalizing input data to 68different types, such as from `List` to `ImmutableList`. 69 70In this event, you can still *discourage* calling the constructor by marking it 71deprecated. More on this [below](#deprecating). 72 73Clever ways do exist to make calling the constructor impossible, but it's 74probably simpler to keep using AutoValue. 75 76### Superclass 77 78The superclass of a record is always `java.lang.Record`. Occasionally the 79superclass of an AutoValue class is something other than `Object`, for example 80when two AutoValue classes share a subset of their properties. 81 82You might still be able to convert to records if you can convert these classes 83into interfaces. 84 85### Derived properties 86 87Records can't have instance fields (other than their properties). So it is hard 88to cache a derived property, for example. AutoValue makes this trivial with 89[`@Memoized`](https://javadoc.io/static/com.google.auto.value/auto-value-annotations/1.10/com/google/auto/value/extension/memoized/Memoized.html). 90 91We suggest ways to achieve the same effect with records [below](#derived), but 92it might be simpler to stick with AutoValue. 93 94### Primitive array properties 95 96AutoValue allows properties of primitive array types such as `byte[]` or `int[]` 97and it will implement `equals` and `hashCode` using the methods of 98`java.util.Arrays`. Records do not have any special treatment for primitive 99arrays, so by default they will use the `equals` and `hashCode` methods of the 100arrays. So two distinct arrays will never compare equal even if they have the 101same contents. 102 103The best way to avoid this problem is not to have properties with primitive 104array type, perhaps using alternatives such as 105[`ImmutableIntArray`](http://guava.dev/ImmutableIntArray). Alternatively you can 106define custom implementations of `equals` and `hashCode` as described in the 107[section](#eqhc) on that topic. But again, you might prefer to keep using 108AutoValue. 109 110(AutoValue doesn't allow properties of non-primitive array types.) 111 112## Translating an AutoValue class into a record 113 114Suppose you have existing AutoValue classes that you do want to translate into 115records, and the [above reasons](#whynot) not to don't apply. What does the 116translation look like? 117 118One important difference is that AutoValue does not allow properties to be 119`null` unless they are marked `@Nullable`. Records require explicit null checks 120to achieve the same effect, typically with 121[`Objects.requireNonNull`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Objects.html#requireNonNull\(T\)). 122 123This might also be a good time to start using a nullness-analysis tool on your 124code; see [NullAway](https://github.com/uber/NullAway) for example. 125 126The examples below show some before-and-after for various migration scenarios. 127For brevity, we've mostly omitted the javadoc comments that good code should 128have on its public classes and methods. 129 130### Basic example with only primitive properties 131 132Before: 133 134```java 135@AutoValue 136public abstract class Point { 137 public abstract int x(); 138 public abstract int y(); 139 140 public static Point of(int x, int y) { 141 return new AutoValue_Point(x, y); 142 } 143} 144``` 145 146After: 147 148```java 149public record Point(int x, int y) { 150 /** @deprecated Call the constructor directly. */ 151 @Deprecated 152 public static Point of(int x, int y) { 153 return new Point(x, y); 154 } 155} 156``` 157 158The static factory method `of` is retained so clients of the `Point` class don't 159have to be updated. If possible, you should migrate clients to call `new 160Point(...)` instead. Then the record can be as simple as this: 161 162```java 163public record Point(int x, int y) {} 164``` 165 166We've omitted the static factory methods from the other examples, but the 167general approach applies: keep the method initially but deprecate it and change 168its body so it just calls the constructor; migrate the callers so they call the 169constructor directly; delete the method. You might be able to use the 170[`InlineMe`](https://errorprone.info/docs/inlineme) mechanism from the Error 171Prone project to encourage this migration: 172 173```java 174package com.example.geometry; 175 176public record Point(int x, int y) { 177 /** @deprecated Call the constructor directly. */ 178 @Deprecated 179 @InlineMe(replacement = "new Point(x, y)", imports = "com.example.geometry.Point") 180 public static Point of(int x, int y) { 181 return new Point(x, y); 182 } 183} 184``` 185 186### Non-primitive properties that are not `@Nullable` 187 188Before: 189 190```java 191@AutoValue 192public abstract class Person { 193 public abstract String name(); 194 public abstract int id(); 195 196 public static Person create(String name, int id) { 197 return new AutoValue_Person(name, id); 198 } 199} 200``` 201 202After: 203 204```java 205public record Person(String name, int id) { 206 public Person { 207 Objects.requireNonNull(name, "name"); 208 } 209} 210``` 211 212### Non-primitive properties that are all `@Nullable` 213 214Before: 215 216```java 217@AutoValue 218public abstract class Person { 219 public abstract @Nullable String name(); 220 public abstract int id(); 221 222 public static Person create(@Nullable String name, int id) { 223 return new AutoValue_Person(name, id); 224 } 225} 226``` 227 228After: 229 230```java 231public record Person(@Nullable String name, int id) {} 232``` 233 234### Validation 235 236Before: 237 238```java 239@AutoValue 240public abstract class Person { 241 public abstract String name(); 242 public abstract int id(); 243 244 public static Person create(String name, int id) { 245 if (id <= 0) { 246 throw new IllegalArgumentException("Id must be positive: " + id); 247 } 248 return new AutoValue_Person(name, id); 249 } 250} 251``` 252 253After: 254 255```java 256public record Person(String name, int id) { 257 public Person { 258 Objects.requireNonNull(name, "name"); 259 if (id <= 0) { 260 throw new IllegalArgumentException("Id must be positive: " + id); 261 } 262 } 263} 264``` 265 266### Normalization 267 268With records, you can rewrite the constructor parameters to apply normalization 269or canonicalization rules. 270 271In this example we have two `int` values, but we don't care which order they are 272supplied in. Therefore we have to put them in a standard order, or else `equals` 273won't behave as expected. 274 275Before: 276 277```java 278@AutoValue 279public abstract class UnorderedPair { 280 public abstract int left(); 281 public abstract int right(); 282 283 public static UnorderedPair of(int left, int right) { 284 int min = Math.min(left, right); 285 int max = Math.max(left, right); 286 return new AutoValue_UnorderedPair(min, max); 287 } 288} 289``` 290 291After: 292 293```java 294public record UnorderedPair(int left, int right) { 295 public UnorderedPair { 296 int min = Math.min(left, right); 297 int max = Math.max(left, right); 298 left = min; 299 right = max; 300 } 301} 302``` 303 304If your normalization results in different types (or more or fewer separate 305fields) than the parameters, you will need to keep the static factory method. On 306a more subtle note, the user of this record might be surprised that what they 307passed in as `left` doesn't always come out as `left()`; keeping the static 308factory method would also allow the parameters to be named differently. See the 309section on the [static factory](#staticfactory) method. 310 311### <a id="beans"></a> JavaBeans prefixes (`getFoo()`) 312 313AutoValue allows you to prefix every property getter with `get`, but records 314don't have any special treatment here. Imagine you have a class like this: 315 316```java 317@AutoValue 318public abstract class Person { 319 public abstract String getName(); 320 public abstract int getId(); 321 322 public static Person create(String name, int id) { 323 return new AutoValue_Person(name, id); 324 } 325} 326``` 327 328The names of the fields in `Person`, and the names in its `toString()`, don't 329have the `get` prefix: 330 331``` 332jshell> Person.create("Priz", 6) 333$1 ==> Person{name=Priz, id=6} 334jshell> $1.getName() 335$2 ==> Priz 336jshell> List<String> showFields(Class<?> c) { 337 ...> return Arrays.stream(c.getDeclaredFields()).map(Field::getName).toList(); 338 ...> } 339jshell> showFields($1.getClass()) 340$3 ==> [name, id] 341``` 342 343You can translate this directly to a record if you don't mind a slightly strange 344`toString()`, and strange field names from reflection and debuggers: 345 346```java 347public record Person(String getName, int getId) { 348 public Person { 349 Objects.requireNonNull(getName); 350 } 351} 352``` 353 354``` 355jshell> Person.create("Priz", 6) 356$1 ==> Person[getName=Priz, getId=6] 357jshell> $1.getName() 358$2 ==> Priz 359jshell> showFields($1.getClass()) 360$3 ==> [getName, getId] 361``` 362 363Alternatively, you can alias `Person.getName()` to be `Person.name()`, etc.: 364 365```java 366public record Person(String name, int id) { 367 public Person { 368 Objects.requireNonNull(name); 369 } 370 371 public String getName() { 372 return name(); 373 } 374 375 public int getId() { 376 return id(); 377 } 378} 379``` 380 381So both `Person.getName()` and `Person.name()` are allowed. You might want to 382deprecate the `get-` methods so you can eventually remove them. 383 384### <a id="derived"></a> Caching derived properties 385 386A record has an instance field for each of its properties, but cannot have other 387instance fields. That means in particular that it is not easy to cache derived 388properties, as you can with AutoValue and [`@Memoized`](howto.md#memoize). 389 390Records *can* have static fields, so one way to cache derived properties is to 391map from record instances to their derived properties. 392 393Before: 394 395```java 396@AutoValue 397public abstract class Person { 398 public abstract String name(); 399 public abstract int id(); 400 401 @Memoized 402 public UUID derivedProperty() { 403 return expensiveFunction(this); 404 } 405 406 public static Person create(String name, int id) { 407 return new AutoValue_Person(name, id); 408 } 409} 410``` 411 412After: 413 414```java 415public record Person(String name, int id) { 416 public Person { 417 Objects.requireNonNull(name); 418 } 419 420 private static final Map<Person, String> derivedPropertyCache = new WeakHashMap<>(); 421 422 public UUID derivedProperty() { 423 synchronized (derivedPropertyCache) { 424 return derivedPropertyCache.computeIfAbsent(this, person -> expensiveFunction(person))); 425 } 426 } 427} 428``` 429 430It's very important to use **`WeakHashMap`** (or similar) or you might suffer a 431memory leak. As usual with `WeakHashMap`, you have to be sure that the values in 432the map don't reference the keys. For more caching options, consider using 433[Caffeine](https://github.com/ben-manes/caffeine). 434 435You might decide that AutoValue with `@Memoized` is simpler than records for 436this case, though. 437 438### Builders 439 440Builders are still available when using records. Instead of 441`@AutoValue.Builder`, you use [`@AutoBuilder`](autobuilder.md). 442 443Before: 444 445```java 446@AutoValue 447public abstract class Person { 448 public abstract String name(); 449 public abstract int id(); 450 451 public static Builder builder() { 452 return new AutoValue_Person.Builder(); 453 } 454 455 @AutoValue.Builder 456 public interface Builder { 457 Builder name(String name); 458 Builder id(int id); 459 Person build(); 460 } 461} 462 463Person p = Person.builder().name("Priz").id(6).build(); 464``` 465 466After: 467 468```java 469public record Person(String name, int id) { 470 public static Builder builder() { 471 return new AutoBuilder_Person_Builder(); 472 } 473 474 @AutoBuilder 475 public interface Builder { 476 Builder name(String name); 477 Builder id(int id); 478 Person build(); 479 } 480} 481 482Person p = Person.builder().name("Priz").id(6).build(); 483``` 484 485#### <a id="deprecating"></a>Deprecating the constructor 486 487As mentioned [above](#staticfactory), the primary constructor is always visible. 488In the preceding example, the builder will enforce that the `name` property is 489not null (since it is not marked @Nullable), but someone calling the constructor 490will bypass that check. You could deprecate the constructor to discourage this: 491 492```java 493public record Person(String name, int id) { 494 /** @deprecated Obtain instances using the {@link #builder()} instead. */ 495 @Deprecated 496 public Person {} 497 498 public static Builder builder() { 499 return new AutoBuilder_Person_Builder(); 500 } 501 502 @AutoBuilder 503 public interface Builder { 504 Builder name(String name); 505 Builder id(int id); 506 Person build(); 507 } 508} 509``` 510 511### Custom `toString()` 512 513A record can define its own `toString()` in exactly the same way as an AutoValue 514class. 515 516### <a id="eqhc"></a> Custom `equals` and `hashCode` 517 518As with AutoValue, it's unusual to want to change the default implementations of 519these methods, and if you do you run the risk of making subtle mistakes. Anyway, 520the idea is the same with both AutoValue and records. 521 522Before: 523 524```java 525@AutoValue 526public abstract class Person { 527 ... 528 529 @Override public boolean equals(Object o) { 530 return o instanceof Person that 531 && Ascii.equalsIgnoreCase(this.name(), that.name()) 532 && this.id() == that.id(); 533 } 534 535 @Override public int hashCode() { 536 return Objects.hash(Ascii.toLowerCase(name()), id()); 537 } 538} 539``` 540 541After: 542 543```java 544public record Person(String name, int id) { 545 ... 546 547 @Override public boolean equals(Object o) { 548 return o instanceof Person that 549 && Ascii.equalsIgnoreCase(this.name, that.name) 550 && this.id == that.id; 551 } 552 553 @Override public int hashCode() { 554 return Objects.hash(Ascii.toLowerCase(name), id); 555 } 556} 557``` 558 559With records, the methods can access fields directly or use the corresponding 560methods (`this.name` or `this.name()`). 561