Generated immutable value classes for Java 8+ using annotation processing
—
The @SerializableAutoValue annotation generates serializable implementations for AutoValue classes that contain non-serializable fields like Optional, ImmutableList, or other modern Java types.
@SerializableAutoValue
@AutoValue
public abstract class Person implements Serializable {
public abstract String name();
public abstract Optional<String> email();
public abstract ImmutableList<String> hobbies();
public static Person create(String name, Optional<String> email, ImmutableList<String> hobbies) {
return new AutoValue_Person(name, email, hobbies);
}
}The generated code automatically handles serialization of:
Optional<T> fieldsImmutableList<T>, ImmutableSet<T>, ImmutableMap<K,V>Person person = Person.create(
"Alice",
Optional.of("alice@example.com"),
ImmutableList.of("reading", "coding", "hiking"));
// Serialize to bytes
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(person);
byte[] serialized = baos.toByteArray();
// Deserialize from bytes
ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
ObjectInputStream ois = new ObjectInputStream(bais);
Person deserialized = (Person) ois.readObject();
System.out.println(person.equals(deserialized)); // trueThe serialization extension automatically handles these non-serializable types:
@SerializableAutoValue
@AutoValue
public abstract class TypesExample implements Serializable {
// Optional types
public abstract Optional<String> optionalString();
public abstract OptionalInt optionalInt();
public abstract OptionalLong optionalLong();
public abstract OptionalDouble optionalDouble();
// Guava Immutable Collections
public abstract ImmutableList<String> list();
public abstract ImmutableSet<Integer> set();
public abstract ImmutableMap<String, Object> map();
public abstract ImmutableMultimap<String, String> multimap();
public abstract ImmutableTable<String, String, Object> table();
// Other Guava types
public abstract ImmutableRangeSet<Integer> rangeSet();
public abstract ImmutableRangeMap<Integer, String> rangeMap();
public static TypesExample create(
Optional<String> optionalString,
OptionalInt optionalInt,
OptionalLong optionalLong,
OptionalDouble optionalDouble,
ImmutableList<String> list,
ImmutableSet<Integer> set,
ImmutableMap<String, Object> map,
ImmutableMultimap<String, String> multimap,
ImmutableTable<String, String, Object> table,
ImmutableRangeSet<Integer> rangeSet,
ImmutableRangeMap<Integer, String> rangeMap) {
return new AutoValue_TypesExample(
optionalString, optionalInt, optionalLong, optionalDouble,
list, set, map, multimap, table, rangeSet, rangeMap);
}
}SerializableAutoValue works with nested AutoValue objects:
@SerializableAutoValue
@AutoValue
public abstract class Address implements Serializable {
public abstract String street();
public abstract String city();
public abstract Optional<String> zipCode();
public static Address create(String street, String city, Optional<String> zipCode) {
return new AutoValue_Address(street, city, zipCode);
}
}
@SerializableAutoValue
@AutoValue
public abstract class Company implements Serializable {
public abstract String name();
public abstract Address headquarters();
public abstract ImmutableList<Address> offices();
public abstract ImmutableMap<String, Optional<String>> contacts();
public static Company create(
String name,
Address headquarters,
ImmutableList<Address> offices,
ImmutableMap<String, Optional<String>> contacts) {
return new AutoValue_Company(name, headquarters, offices, contacts);
}
}Create custom serializers for types not supported by default:
// Custom type that needs serialization support
public class CustomType {
private final String data;
public CustomType(String data) {
this.data = data;
}
public String getData() {
return data;
}
}
// Custom serializer extension
public class CustomTypeSerializerExtension implements SerializerExtension {
@Override
public Optional<Serializer> getSerializer(
TypeMirror typeMirror,
SerializerFactory factory) {
if (isCustomType(typeMirror)) {
return Optional.of(new CustomTypeSerializer());
}
return Optional.empty();
}
private boolean isCustomType(TypeMirror type) {
return type.toString().equals(CustomType.class.getName());
}
private static class CustomTypeSerializer implements Serializer {
@Override
public TypeMirror proxyFieldType() {
return getTypeMirror(String.class);
}
@Override
public CodeBlock toProxy(CodeBlock expression) {
return CodeBlock.of("$L.getData()", expression);
}
@Override
public CodeBlock fromProxy(CodeBlock expression) {
return CodeBlock.of("new $T($L)", CustomType.class, expression);
}
}
}Register the extension in META-INF/services/com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension.
SerializableAutoValue works seamlessly with builders:
@SerializableAutoValue
@AutoValue
public abstract class Configuration implements Serializable {
public abstract String host();
public abstract Optional<Integer> port();
public abstract ImmutableSet<String> features();
public abstract ImmutableMap<String, String> properties();
public static Builder builder() {
return new AutoValue_Configuration.Builder()
.features(ImmutableSet.of())
.properties(ImmutableMap.of());
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder host(String host);
public abstract Builder port(int port);
public abstract Builder features(Iterable<String> features);
public abstract Builder properties(Map<String, String> properties);
public abstract Builder addFeature(String feature);
public abstract Builder putProperty(String key, String value);
public abstract Configuration build();
}
}Serialization works with builder-created objects:
Configuration config = Configuration.builder()
.host("localhost")
.port(8080)
.addFeature("ssl")
.addFeature("compression")
.putProperty("timeout", "5000")
.build();
// Serialize and deserialize
byte[] serialized = serialize(config);
Configuration deserialized = deserialize(serialized);
System.out.println(config.equals(deserialized)); // trueSerialization supports generic types:
@SerializableAutoValue
@AutoValue
public abstract class Container<T> implements Serializable {
public abstract T value();
public abstract Optional<String> label();
public abstract ImmutableList<T> alternatives();
public static <T> Container<T> create(
T value,
Optional<String> label,
ImmutableList<T> alternatives) {
return new AutoValue_Container<>(value, label, alternatives);
}
}Usage with serialization:
Container<String> stringContainer = Container.create(
"main value",
Optional.of("primary"),
ImmutableList.of("alt1", "alt2"));
// Serialization preserves generic type information
byte[] serialized = serialize(stringContainer);
Container<String> deserialized = deserialize(serialized);SerializableAutoValue provides clear errors for unsupported types:
@SerializableAutoValue
@AutoValue
public abstract class UnsupportedExample implements Serializable {
// This will cause a compilation error
public abstract NonSerializableCustomType customField(); // ERROR: No serializer found
public static UnsupportedExample create(NonSerializableCustomType customField) {
return new AutoValue_UnsupportedExample(customField);
}
}The error message will indicate which type needs a custom serializer.
The generated serialization code is optimized:
Handle serialization version compatibility:
@SerializableAutoValue
@AutoValue
public abstract class VersionedData implements Serializable {
private static final long serialVersionUID = 1L;
public abstract String name();
public abstract Optional<String> description();
public abstract ImmutableList<String> tags();
// Method to handle version compatibility
private Object readResolve() {
// Custom logic for handling old versions
return this;
}
public static VersionedData create(String name, Optional<String> description, ImmutableList<String> tags) {
return new AutoValue_VersionedData(name, description, tags);
}
}SerializableAutoValue can be combined with other extensions:
@SerializableAutoValue
@AutoValue
public abstract class CachedSerializableData implements Serializable {
public abstract String data();
public abstract Optional<ImmutableList<Integer>> values();
@Memoized
public String processedData() {
return data().toUpperCase().trim();
}
@Memoized
@Override
public abstract String toString();
public static CachedSerializableData create(String data, Optional<ImmutableList<Integer>> values) {
return new AutoValue_CachedSerializableData(data, values);
}
}The memoized fields are properly handled during serialization/deserialization.
SerializableInstall with Tessl CLI
npx tessl i tessl/maven-com-google-auto-value--auto-value