# AutoValue and Java Records
Starting with Java 16,
[records](https://docs.oracle.com/en/java/javase/19/language/records.html) are a
standard feature of the language. If records are available to you, is there any
reason to use AutoValue?
## The short answer
Generally, **use records** when you can. They have a very concise and readable
syntax, they produce less code, and they don't need any special configuration or
dependency. They are obviously a better choice when your class is just an
aggregation of values, for example to allow a method to return multiple values
or to combine values into a map key.
(This was by design: the AutoValue authors were part of the
[Project Amber](https://openjdk.org/projects/amber/) working group, where our
goal was to make the records feature the best AutoValue replacement it could
be.)
If you have existing code that has AutoValue classes, you might want to migrate
some or all of those classes to be records instead. In this document we will
explain how to do this, and in what cases you might prefer not to.
## Can't use Java records yet?
If you're creating new AutoValue classes for Java 15 or earlier, **follow this
advice** to make sure your future conversion to records will be straightforward:
* Extend `Object` only (implementing interfaces is fine).
* Don't use JavaBeans-style prefixes: use `abstract int bar()`, not `abstract
int getBar()`.
* Don't declare any non-static fields of your own.
* Give the factory method and accessors the same visibility level as the
class.
* Avoid using [extensions](extensions.md).
Adopting AutoValue at this time is still a good idea! There is no better way to
make sure your code is as ready as possible to migrate to records later.
## Reasons to stick with AutoValue
While records are usually better, there are some AutoValue features that have no
simple equivalent with records. So you might prefer not to try migrating
AutoValue classes that use those features, and you might even sometimes make new
AutoValue classes even if records are available to you.
### Extensions
AutoValue has [extensions](extensions.md). Some are built in, like the
[`@Memoized`](https://javadoc.io/static/com.google.auto.value/auto-value-annotations/1.10/com/google/auto/value/extension/memoized/Memoized.html),
[`@ToPrettyString`](https://javadoc.io/static/com.google.auto.value/auto-value-annotations/1.10/com/google/auto/value/extension/toprettystring/ToPrettyString.html),
and
[`@SerializableAutoValue`](https://javadoc.io/static/com.google.auto.value/auto-value-annotations/1.10/com/google/auto/value/extension/serializable/SerializableAutoValue.html)
extensions. Most extensions will have no real equivalent with records.
### Keeping the static factory method
AutoValue has very few API-visible "quirks", but one is that it forces you to
use a static factory method as your class's creation API. A record can have this
too, but it can't prevent its constructor from *also* being visible, and
exposing two ways to do the same thing can be dangerous.
We think most users will be happy to switch to constructors and drop the factory
methods, but you might want to keep a factory method in some records. Perhaps
for compatibility reasons, or because you are normalizing input data to
different types, such as from `List` to `ImmutableList`.
In this event, you can still *discourage* calling the constructor by marking it
deprecated. More on this [below](#deprecating).
Clever ways do exist to make calling the constructor impossible, but it's
probably simpler to keep using AutoValue.
### Superclass
The superclass of a record is always `java.lang.Record`. Occasionally the
superclass of an AutoValue class is something other than `Object`, for example
when two AutoValue classes share a subset of their properties.
You might still be able to convert to records if you can convert these classes
into interfaces.
### Derived properties
Records can't have instance fields (other than their properties). So it is hard
to cache a derived property, for example. AutoValue makes this trivial with
[`@Memoized`](https://javadoc.io/static/com.google.auto.value/auto-value-annotations/1.10/com/google/auto/value/extension/memoized/Memoized.html).
We suggest ways to achieve the same effect with records [below](#derived), but
it might be simpler to stick with AutoValue.
### Primitive array properties
AutoValue allows properties of primitive array types such as `byte[]` or `int[]`
and it will implement `equals` and `hashCode` using the methods of
`java.util.Arrays`. Records do not have any special treatment for primitive
arrays, so by default they will use the `equals` and `hashCode` methods of the
arrays. So two distinct arrays will never compare equal even if they have the
same contents.
The best way to avoid this problem is not to have properties with primitive
array type, perhaps using alternatives such as
[`ImmutableIntArray`](http://guava.dev/ImmutableIntArray). Alternatively you can
define custom implementations of `equals` and `hashCode` as described in the
[section](#eqhc) on that topic. But again, you might prefer to keep using
AutoValue.
(AutoValue doesn't allow properties of non-primitive array types.)
## Translating an AutoValue class into a record
Suppose you have existing AutoValue classes that you do want to translate into
records, and the [above reasons](#whynot) not to don't apply. What does the
translation look like?
One important difference is that AutoValue does not allow properties to be
`null` unless they are marked `@Nullable`. Records require explicit null checks
to achieve the same effect, typically with
[`Objects.requireNonNull`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Objects.html#requireNonNull\(T\)).
This might also be a good time to start using a nullness-analysis tool on your
code; see [NullAway](https://github.com/uber/NullAway) for example.
The examples below show some before-and-after for various migration scenarios.
For brevity, we've mostly omitted the javadoc comments that good code should
have on its public classes and methods.
### Basic example with only primitive properties
Before:
```java
@AutoValue
public abstract class Point {
public abstract int x();
public abstract int y();
public static Point of(int x, int y) {
return new AutoValue_Point(x, y);
}
}
```
After:
```java
public record Point(int x, int y) {
/** @deprecated Call the constructor directly. */
@Deprecated
public static Point of(int x, int y) {
return new Point(x, y);
}
}
```
The static factory method `of` is retained so clients of the `Point` class don't
have to be updated. If possible, you should migrate clients to call `new
Point(...)` instead. Then the record can be as simple as this:
```java
public record Point(int x, int y) {}
```
We've omitted the static factory methods from the other examples, but the
general approach applies: keep the method initially but deprecate it and change
its body so it just calls the constructor; migrate the callers so they call the
constructor directly; delete the method. You might be able to use the
[`InlineMe`](https://errorprone.info/docs/inlineme) mechanism from the Error
Prone project to encourage this migration:
```java
package com.example.geometry;
public record Point(int x, int y) {
/** @deprecated Call the constructor directly. */
@Deprecated
@InlineMe(replacement = "new Point(x, y)", imports = "com.example.geometry.Point")
public static Point of(int x, int y) {
return new Point(x, y);
}
}
```
### Non-primitive properties that are not `@Nullable`
Before:
```java
@AutoValue
public abstract class Person {
public abstract String name();
public abstract int id();
public static Person create(String name, int id) {
return new AutoValue_Person(name, id);
}
}
```
After:
```java
public record Person(String name, int id) {
public Person {
Objects.requireNonNull(name, "name");
}
}
```
### Non-primitive properties that are all `@Nullable`
Before:
```java
@AutoValue
public abstract class Person {
public abstract @Nullable String name();
public abstract int id();
public static Person create(@Nullable String name, int id) {
return new AutoValue_Person(name, id);
}
}
```
After:
```java
public record Person(@Nullable String name, int id) {}
```
### Validation
Before:
```java
@AutoValue
public abstract class Person {
public abstract String name();
public abstract int id();
public static Person create(String name, int id) {
if (id <= 0) {
throw new IllegalArgumentException("Id must be positive: " + id);
}
return new AutoValue_Person(name, id);
}
}
```
After:
```java
public record Person(String name, int id) {
public Person {
Objects.requireNonNull(name, "name");
if (id <= 0) {
throw new IllegalArgumentException("Id must be positive: " + id);
}
}
}
```
### Normalization
With records, you can rewrite the constructor parameters to apply normalization
or canonicalization rules.
In this example we have two `int` values, but we don't care which order they are
supplied in. Therefore we have to put them in a standard order, or else `equals`
won't behave as expected.
Before:
```java
@AutoValue
public abstract class UnorderedPair {
public abstract int left();
public abstract int right();
public static UnorderedPair of(int left, int right) {
int min = Math.min(left, right);
int max = Math.max(left, right);
return new AutoValue_UnorderedPair(min, max);
}
}
```
After:
```java
public record UnorderedPair(int left, int right) {
public UnorderedPair {
int min = Math.min(left, right);
int max = Math.max(left, right);
left = min;
right = max;
}
}
```
If your normalization results in different types (or more or fewer separate
fields) than the parameters, you will need to keep the static factory method. On
a more subtle note, the user of this record might be surprised that what they
passed in as `left` doesn't always come out as `left()`; keeping the static
factory method would also allow the parameters to be named differently. See the
section on the [static factory](#staticfactory) method.
### JavaBeans prefixes (`getFoo()`)
AutoValue allows you to prefix every property getter with `get`, but records
don't have any special treatment here. Imagine you have a class like this:
```java
@AutoValue
public abstract class Person {
public abstract String getName();
public abstract int getId();
public static Person create(String name, int id) {
return new AutoValue_Person(name, id);
}
}
```
The names of the fields in `Person`, and the names in its `toString()`, don't
have the `get` prefix:
```
jshell> Person.create("Priz", 6)
$1 ==> Person{name=Priz, id=6}
jshell> $1.getName()
$2 ==> Priz
jshell> List showFields(Class> c) {
...> return Arrays.stream(c.getDeclaredFields()).map(Field::getName).toList();
...> }
jshell> showFields($1.getClass())
$3 ==> [name, id]
```
You can translate this directly to a record if you don't mind a slightly strange
`toString()`, and strange field names from reflection and debuggers:
```java
public record Person(String getName, int getId) {
public Person {
Objects.requireNonNull(getName);
}
}
```
```
jshell> Person.create("Priz", 6)
$1 ==> Person[getName=Priz, getId=6]
jshell> $1.getName()
$2 ==> Priz
jshell> showFields($1.getClass())
$3 ==> [getName, getId]
```
Alternatively, you can alias `Person.getName()` to be `Person.name()`, etc.:
```java
public record Person(String name, int id) {
public Person {
Objects.requireNonNull(name);
}
public String getName() {
return name();
}
public int getId() {
return id();
}
}
```
So both `Person.getName()` and `Person.name()` are allowed. You might want to
deprecate the `get-` methods so you can eventually remove them.
### Caching derived properties
A record has an instance field for each of its properties, but cannot have other
instance fields. That means in particular that it is not easy to cache derived
properties, as you can with AutoValue and [`@Memoized`](howto.md#memoize).
Records *can* have static fields, so one way to cache derived properties is to
map from record instances to their derived properties.
Before:
```java
@AutoValue
public abstract class Person {
public abstract String name();
public abstract int id();
@Memoized
public UUID derivedProperty() {
return expensiveFunction(this);
}
public static Person create(String name, int id) {
return new AutoValue_Person(name, id);
}
}
```
After:
```java
public record Person(String name, int id) {
public Person {
Objects.requireNonNull(name);
}
private static final Map derivedPropertyCache = new WeakHashMap<>();
public UUID derivedProperty() {
synchronized (derivedPropertyCache) {
return derivedPropertyCache.computeIfAbsent(this, person -> expensiveFunction(person)));
}
}
}
```
It's very important to use **`WeakHashMap`** (or similar) or you might suffer a
memory leak. As usual with `WeakHashMap`, you have to be sure that the values in
the map don't reference the keys. For more caching options, consider using
[Caffeine](https://github.com/ben-manes/caffeine).
You might decide that AutoValue with `@Memoized` is simpler than records for
this case, though.
### Builders
Builders are still available when using records. Instead of
`@AutoValue.Builder`, you use [`@AutoBuilder`](autobuilder.md).
Before:
```java
@AutoValue
public abstract class Person {
public abstract String name();
public abstract int id();
public static Builder builder() {
return new AutoValue_Person.Builder();
}
@AutoValue.Builder
public interface Builder {
Builder name(String name);
Builder id(int id);
Person build();
}
}
Person p = Person.builder().name("Priz").id(6).build();
```
After:
```java
public record Person(String name, int id) {
public static Builder builder() {
return new AutoBuilder_Person_Builder();
}
@AutoBuilder
public interface Builder {
Builder name(String name);
Builder id(int id);
Person build();
}
}
Person p = Person.builder().name("Priz").id(6).build();
```
#### Deprecating the constructor
As mentioned [above](#staticfactory), the primary constructor is always visible.
In the preceding example, the builder will enforce that the `name` property is
not null (since it is not marked @Nullable), but someone calling the constructor
will bypass that check. You could deprecate the constructor to discourage this:
```java
public record Person(String name, int id) {
/** @deprecated Obtain instances using the {@link #builder()} instead. */
@Deprecated
public Person {}
public static Builder builder() {
return new AutoBuilder_Person_Builder();
}
@AutoBuilder
public interface Builder {
Builder name(String name);
Builder id(int id);
Person build();
}
}
```
### Custom `toString()`
A record can define its own `toString()` in exactly the same way as an AutoValue
class.
### Custom `equals` and `hashCode`
As with AutoValue, it's unusual to want to change the default implementations of
these methods, and if you do you run the risk of making subtle mistakes. Anyway,
the idea is the same with both AutoValue and records.
Before:
```java
@AutoValue
public abstract class Person {
...
@Override public boolean equals(Object o) {
return o instanceof Person that
&& Ascii.equalsIgnoreCase(this.name(), that.name())
&& this.id() == that.id();
}
@Override public int hashCode() {
return Objects.hash(Ascii.toLowerCase(name()), id());
}
}
```
After:
```java
public record Person(String name, int id) {
...
@Override public boolean equals(Object o) {
return o instanceof Person that
&& Ascii.equalsIgnoreCase(this.name, that.name)
&& this.id == that.id;
}
@Override public int hashCode() {
return Objects.hash(Ascii.toLowerCase(name), id);
}
}
```
With records, the methods can access fields directly or use the corresponding
methods (`this.name` or `this.name()`).