Your task is to migrate classes from AutoValue to Java records, and modify any code using them as needed until the tests pass.
Replace the class itself with the equivalent Java record. Make sure the record fields match the fields defined in AutoValue. If the class had any other functionality such as static methods etc, add those to the record.
If the AutoValue class provided a Builder, use AutoMatter instead to create a builder for the record.
Remove any pre-existing builder class. Update any dependent code to use this.
You can include AutoMatter for a record like so:
import io.norberg.automatter.AutoMatter;
@AutoMatter
record Foo [...]The AutoMatter annotation on a record "Foo" will create a class called "FooBuilder" that any dependent code can use:
Foo foo = new FooBuilder()
.value("some string")
.build();AutoMatter builders don't use "set" prefix. Update all builder method calls: setUri() → uri(), setMetadata() → metadata(), etc.
A builder can be created from an existing value using the from() method:
Foo foo = ...
Foo newFoo = FooBuilder.from(foo)
.value("some string")
.build();If the record contains collection types (List, Set, Map etc), AutoMatter will generate addX and putX methods like so:
record Foo(
List<Integer> lengths,
Map<String, Integer> ages
)
Foo foo = FooBuilder.builder()
.addLength(17)
.putAge("hello", 42)
.build()The collection builders will use the singular form of the field name, so lengths becomes addLength.
Unlike AutoValue, Records don't have a toBuilder() method by default. Replace them with AutoMatter's from() method:
Record record = ...
RecordBuilder builder = RecordBuilder.from(record);If AutoMatter is used from a Bazel project, make sure to add the following to the DEPS list in BUILD.bazel if it doesn't already exist:
"@maven//:io_norberg_auto_matter_annotation",
as well as a plugin in the service_java macro in BUILD.bazel if it doesn't already exist:
plugins = [
"//tools/javac_plugins:automatter",
],
If AutoValue is no longer needed, clean up the BUILD.bazel file to remove the dependency (@maven//:com_google_auto_value_auto_value_annotations) and plugin (//tools/javac_plugins:autovalue).
Make sure to retain any @JsonProperty annotations.
If there are Jackson annotations to use a builder for deserialization, e.g. @JsonDeserialize(builder = Foo.Builder.class), this can safely be removed.
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.
Records can have static fields, so one way to cache derived properties is to map from record instances to their derived properties.
Before:
@AutoValue
public abstract class Person {
public abstract String name();
@Memoized
public UUID derivedProperty() {
return expensiveFunction(this);
}
public static Person create(String name, int id) {
return new AutoValue_Person(name, id);
}
}After:
public record Person(String name) {
private static final Map<Person, String> derivedPropertyCache = new WeakHashMap<>();
public UUID derivedProperty() {
synchronized (derivedPropertyCache) {
return derivedPropertyCache.computeIfAbsent(this, person -> expensiveFunction(person));
}
}
}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.
- Don't change or remove comments unless they are no longer relevant after your changes
- Don't add comments about future changes such as
"// If in the future <X> is needed ..." - Don't comment out code, remove the lines which are not needed
- Don't add comments on imports
- Keep changes only to what is absolutely needed, don't touch code or comments unrelated to the AutoValue migration
- If the build uses an older Java version that doesn't support records (e.g. Java 11 or Java 14), do not attempt the migration. Just leave the existing AutValue usage in place.
- Don't create new static methods in the record that weren't there before. But if there were some before (such as
builder()), you can use those from the rest of the code.
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class Foo {
public abstract String foo();
public abstract Set<Bar> bars();
public static Foo create(final String foo, final Set<Bar> bars) {
return new AutoValue_Foo(foo, bars);
}
public static Builder builder() {
return new AutoValue_Foo.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setFoo(final String foo);
public abstract Builder setBars(final Set<Bar> bars);
public abstract Foo build();
}
} import io.norberg.automatter.AutoMatter;
@AutoMatter
public record Foo(String foo, Set<Bar> bars) {
public static Foo create(final String foo, final Set<Bar> bars) {
return new Foo(foo, bars);
}
public static FooBuilder builder() {
return new FooBuilder();
}
} import com.google.auto.value.AutoValue;
@AutoValue
@JsonDeserialize(builder = Foo.Builder.class)
public abstract class Foo {
@JsonProperty("foo_bar")
public abstract String fooBar();
@AutoValue.Builder
public abstract static class Builder {
@JsonProperty("foo_bar")
public abstract Builder setFooBar(final String fooBar);
public abstract Foo build();
}
} import io.norberg.automatter.AutoMatter;
@AutoMatter
public record Foo(
@JsonProperty("foo_bar") String fooBar
) {}import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
@AutoValue
public abstract class FooBar {
abstract ImmutableMap<String, Integer> handlers();
@AutoValue.Builder
public abstract static class Builder {
abstract ImmutableMap.Builder<String, Integer> handlersBuilder();
public Builder addHandler(final String key, final Integer value) {
handlersBuilder().put(key, value);
return this;
}
public abstract FooBar build();
}
public static FooBar createInstance() {
return new AutoValue_FooBar.Builder()
.addHandler("foo", 1)
.addHandler("bar", 2)
.build();
}
} import com.google.common.collect.ImmutableMap;
import io.norberg.automatter.AutoMatter;
@AutoMatter
public record FooBar(ImmutableMap<String, Integer> handlers) {
public static FooBar createInstance() {
return new FooBarBuilder()
.addHandler("foo", 1)
.addHandler("bar", 2)
.build();
}
} import com.google.auto.value.AutoValue;
@AutoValue
public abstract class Person {
public abstract String name();
@Memoized
public UUID derivedProperty() {
return expensiveFunction(this);
}
public static Person create(String name, int id) {
return new AutoValue_Person(name, id);
}
} import java.util.Map;
import java.util.WeakHashMap;
import java.util.UUID;
public record Person(String name) {
private static final Map<Person, UUID> derivedPropertyCache = new WeakHashMap<>();
private static UUID expensiveFunction(Person person) { return java.util.UUID.randomUUID(); }
public UUID derivedProperty() {
synchronized (derivedPropertyCache) {
return derivedPropertyCache.computeIfAbsent(this, person -> expensiveFunction(person));
}
}
} import java.util.Set;
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class Foo {
public abstract String foo();
public abstract Set<Bar> bars();
public static Foo create(final String foo, final Set<Bar> bars) {
return new AutoValue_Foo(foo, bars);
}
} import java.util.Set;
public record Foo(String foo, Set<Bar> bars) {
public static Foo create(final String foo, final Set<Bar> bars) {
return new Foo(foo, bars);
}
} import com.google.auto.value.AutoValue;
@AutoValue
public abstract class Foo {
public abstract String foo();
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setFoo(final String foo);
abstract Foo autoBuild();
public Foo build() {
final Foo foo = autoBuild();
if (foo.foo() == null) {
throw new IllegalStateException("foo cannot be null");
}
return foo;
}
}
} import io.norberg.automatter.AutoMatter;
@AutoMatter
public record Foo(String foo) {
public Foo {
if (foo == null) {
throw new IllegalStateException("foo cannot be null");
}
}
}