Introduction to AutoValue
AutoValue is a Google-generated annotation processor that automatically creates immutable value classes. It eliminates the boilerplate code required for proper equals(), hashCode(), toString(), and other methods while ensuring immutability and thread safety.
Table of Contents
- Setup and Dependencies
- Basic AutoValue Usage
- Advanced Features
- Builder Pattern
- Collections and Custom Types
- Integration with Other Libraries
- Best Practices and Performance
1. Setup and Dependencies
Maven Configuration
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<autovalue.version>1.10.1</autovalue.version>
</properties>
<dependencies>
<!-- AutoValue Annotations -->
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value-annotations</artifactId>
<version>${autovalue.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<version>${autovalue.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
Gradle Configuration
dependencies {
implementation 'com.google.auto.value:auto-value-annotations:1.10.1'
annotationProcessor 'com.google.auto.value:auto-value:1.10.1'
}
2. Basic AutoValue Usage
Simple Value Class
package com.example.autovalue;
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class Person {
// Abstract getters - implementation will be generated
public abstract String name();
public abstract int age();
public abstract String email();
// Factory method to create instances
public static Person create(String name, int age, String email) {
return new AutoValue_Person(name, age, email);
}
}
Usage Example
public class BasicUsage {
public static void main(String[] args) {
// Create instances using factory method
Person person1 = Person.create("John Doe", 30, "[email protected]");
Person person2 = Person.create("Jane Smith", 25, "[email protected]");
// Auto-generated methods work correctly
System.out.println(person1.toString());
// Output: Person{name=John Doe, age=30, [email protected]}
System.out.println("Equals: " + person1.equals(person2)); // false
System.out.println("Hash code: " + person1.hashCode());
// Getters are available
System.out.println("Name: " + person1.name());
System.out.println("Age: " + person1.age());
}
}
With Validation
@AutoValue
public abstract class ValidatedPerson {
public abstract String name();
public abstract int age();
public abstract String email();
public static ValidatedPerson create(String name, int age, String email) {
// Add validation in factory method
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("Name cannot be null or empty");
}
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Age must be between 0 and 150");
}
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("Invalid email address");
}
return new AutoValue_ValidatedPerson(name, age, email);
}
}
3. Advanced AutoValue Features
Optional Fields
import java.util.Optional;
@AutoValue
public abstract class PersonWithOptional {
public abstract String name();
public abstract Optional<String> phoneNumber();
public abstract Optional<String> address();
public static PersonWithOptional create(String name, Optional<String> phoneNumber, Optional<String> address) {
return new AutoValue_PersonWithOptional(name, phoneNumber, address);
}
// Convenience factory methods
public static PersonWithOptional withPhone(String name, String phoneNumber) {
return create(name, Optional.of(phoneNumber), Optional.empty());
}
public static PersonWithOptional minimal(String name) {
return create(name, Optional.empty(), Optional.empty());
}
}
With Default Values
@AutoValue
public abstract class Config {
public abstract String host();
public abstract int port();
public abstract boolean sslEnabled();
public abstract int timeoutMs();
// Static factory method with defaults
public static Config create(String host, int port, boolean sslEnabled, int timeoutMs) {
return new AutoValue_Config(host, port, sslEnabled, timeoutMs);
}
// Builder-like static method with sensible defaults
public static Config withDefaults(String host, int port) {
return create(host, port, true, 30000);
}
}
Inheritance and Composition
// Base class
@AutoValue
public abstract class Entity {
public abstract String id();
public abstract long createdAt();
public static Entity create(String id, long createdAt) {
return new AutoValue_Entity(id, createdAt);
}
}
// Derived class
@AutoValue
public abstract class User extends Entity {
public abstract String username();
public abstract String email();
public static User create(String id, long createdAt, String username, String email) {
return new AutoValue_User(id, createdAt, username, email);
}
}
// Composition example
@AutoValue
public abstract class Order {
public abstract String orderId();
public abstract User customer();
public abstract double amount();
public static Order create(String orderId, User customer, double amount) {
return new AutoValue_Order(orderId, customer, amount);
}
}
4. Builder Pattern with AutoValue
Simple Builder
@AutoValue
public abstract class PersonWithBuilder {
public abstract String name();
public abstract int age();
public abstract String email();
public abstract Optional<String> phone();
public static Builder builder() {
return new AutoValue_PersonWithBuilder.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setName(String name);
public abstract Builder setAge(int age);
public abstract Builder setEmail(String email);
public abstract Builder setPhone(Optional<String> phone);
// Convenience method for phone
public Builder setPhone(String phone) {
return setPhone(Optional.ofNullable(phone));
}
public abstract PersonWithBuilder build();
}
}
Advanced Builder with Validation
@AutoValue
public abstract class AdvancedPerson {
public abstract String name();
public abstract int age();
public abstract String email();
public abstract Optional<String> phone();
public abstract Optional<String> address();
public static Builder builder() {
return new AutoValue_AdvancedPerson.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");
public abstract Builder setName(String name);
public abstract Builder setAge(int age);
public abstract Builder setEmail(String email);
public abstract Builder setPhone(Optional<String> phone);
public abstract Builder setAddress(Optional<String> address);
// Convenience methods
public Builder setPhone(String phone) {
return setPhone(Optional.ofNullable(phone));
}
public Builder setAddress(String address) {
return setAddress(Optional.ofNullable(address));
}
abstract AdvancedPerson autoBuild(); // Package-private build method
public AdvancedPerson build() {
AdvancedPerson person = autoBuild();
// Validation
if (person.name() == null || person.name().trim().isEmpty()) {
throw new IllegalStateException("Name cannot be null or empty");
}
if (person.age() < 0 || person.age() > 150) {
throw new IllegalStateException("Age must be between 0 and 150");
}
if (!EMAIL_PATTERN.matcher(person.email()).matches()) {
throw new IllegalStateException("Invalid email format");
}
return person;
}
}
}
Builder Usage Examples
public class BuilderUsage {
public static void main(String[] args) {
// Basic builder usage
PersonWithBuilder person = PersonWithBuilder.builder()
.setName("John Doe")
.setAge(30)
.setEmail("[email protected]")
.setPhone("+1234567890")
.build();
System.out.println(person);
// Advanced builder with validation
try {
AdvancedPerson invalidPerson = AdvancedPerson.builder()
.setName("")
.setAge(200)
.setEmail("invalid-email")
.build();
} catch (IllegalStateException e) {
System.out.println("Validation failed: " + e.getMessage());
}
// Building with optional fields
AdvancedPerson minimalPerson = AdvancedPerson.builder()
.setName("Jane Smith")
.setAge(25)
.setEmail("[email protected]")
.build(); // phone and address are optional
System.out.println(minimalPerson);
}
}
5. Collections and Custom Types
With Collections
import java.util.List;
import java.util.Set;
import java.util.Map;
@AutoValue
public abstract class Project {
public abstract String name();
public abstract List<String> teamMembers();
public abstract Set<String> technologies();
public abstract Map<String, Integer> taskEstimations(); // task -> hours
// For immutable collections, use Guava's ImmutableList or copy in factory
public static Project create(
String name,
List<String> teamMembers,
Set<String> technologies,
Map<String, Integer> taskEstimations) {
return new AutoValue_Project(
name,
List.copyOf(teamMembers), // Defensive copy for immutability
Set.copyOf(technologies),
Map.copyOf(taskEstimations)
);
}
public static Builder builder() {
return new AutoValue_Project.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setName(String name);
public abstract Builder setTeamMembers(List<String> teamMembers);
public abstract Builder setTechnologies(Set<String> technologies);
public abstract Builder setTaskEstimations(Map<String, Integer> taskEstimations);
public abstract Project build();
}
}
With Custom Types and Nested Objects
@AutoValue
public abstract class Address {
public abstract String street();
public abstract String city();
public abstract String state();
public abstract String zipCode();
public static Address create(String street, String city, String state, String zipCode) {
return new AutoValue_Address(street, city, state, zipCode);
}
}
@AutoValue
public abstract class Employee {
public abstract String id();
public abstract String name();
public abstract Address address();
public abstract Department department();
public abstract double salary();
public static Employee create(String id, String name, Address address,
Department department, double salary) {
return new AutoValue_Employee(id, name, address, department, salary);
}
public static Builder builder() {
return new AutoValue_Employee.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setId(String id);
public abstract Builder setName(String name);
public abstract Builder setAddress(Address address);
public abstract Builder setDepartment(Department department);
public abstract Builder setSalary(double salary);
public abstract Employee build();
}
public enum Department {
ENGINEERING, MARKETING, SALES, HR, FINANCE
}
}
With Copy Methods (Withers)
@AutoValue
public abstract class MutableLikePerson {
public abstract String name();
public abstract int age();
public abstract String email();
public static MutableLikePerson create(String name, int age, String email) {
return new AutoValue_MutableLikePerson(name, age, email);
}
// Copy methods for "modification" - creates new instance
public MutableLikePerson withName(String newName) {
return create(newName, age(), email());
}
public MutableLikePerson withAge(int newAge) {
return create(name(), newAge, email());
}
public MutableLikePerson withEmail(String newEmail) {
return create(name(), age(), newEmail);
}
public static Builder builder() {
return new AutoValue_MutableLikePerson.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setName(String name);
public abstract Builder setAge(int age);
public abstract Builder setEmail(String email);
public abstract MutableLikePerson build();
}
}
6. Integration with Other Libraries
With GSON for JSON Serialization
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.annotations.SerializedName;
@AutoValue
public abstract class GsonPerson {
@SerializedName("full_name")
public abstract String name();
@SerializedName("years_old")
public abstract int age();
public abstract String email();
public static GsonPerson create(String name, int age, String email) {
return new AutoValue_GsonPerson(name, age, email);
}
// GSON TypeAdapter
public static TypeAdapter<GsonPerson> typeAdapter(Gson gson) {
return new AutoValue_GsonPerson.GsonTypeAdapter(gson);
}
}
With Jackson for JSON
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@AutoValue
@JsonSerialize(as = JacksonPerson.class)
@JsonDeserialize(builder = AutoValue_JacksonPerson.Builder.class)
public abstract class JacksonPerson {
@JsonProperty("name")
public abstract String name();
@JsonProperty("age")
public abstract int age();
@JsonProperty("email")
public abstract String email();
public static Builder builder() {
return new AutoValue_JacksonPerson.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
@JsonProperty("name")
public abstract Builder setName(String name);
@JsonProperty("age")
public abstract Builder setAge(int age);
@JsonProperty("email")
public abstract Builder setEmail(String email);
public abstract JacksonPerson build();
}
}
With Protocol Buffers Style
@AutoValue
public abstract class ProtoStylePerson {
// Protocol buffers style naming
public abstract String getName(); // Instead of name()
public abstract int getAge(); // Instead of age()
public abstract String getEmail(); // Instead of email()
// Builder with protocol buffers style
public abstract Builder toBuilder();
public static ProtoStylePerson create(String name, int age, String email) {
return new AutoValue_ProtoStylePerson(name, age, email);
}
public static Builder newBuilder() {
return new AutoValue_ProtoStylePerson.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setName(String name);
public abstract Builder setAge(int age);
public abstract Builder setEmail(String email);
public abstract ProtoStylePerson build();
}
}
7. Complex Real-World Examples
E-Commerce Domain Model
import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;
@AutoValue
public abstract class Order {
public abstract String orderId();
public abstract Customer customer();
public abstract List<OrderItem> items();
public abstract OrderStatus status();
public abstract Instant createdAt();
public abstract Instant updatedAt();
public abstract Address shippingAddress();
public abstract Address billingAddress();
public abstract BigDecimal totalAmount();
public abstract BigDecimal taxAmount();
public abstract BigDecimal shippingCost();
public BigDecimal grandTotal() {
return totalAmount().add(taxAmount()).add(shippingCost());
}
public static Order create(String orderId, Customer customer, List<OrderItem> items,
OrderStatus status, Instant createdAt, Instant updatedAt,
Address shippingAddress, Address billingAddress,
BigDecimal totalAmount, BigDecimal taxAmount,
BigDecimal shippingCost) {
return new AutoValue_Order(orderId, customer, List.copyOf(items), status,
createdAt, updatedAt, shippingAddress, billingAddress,
totalAmount, taxAmount, shippingCost);
}
public static Builder builder() {
return new AutoValue_Order.Builder();
}
// Copy methods for state transitions
public Order withStatus(OrderStatus newStatus) {
return builder()
.setOrderId(orderId())
.setCustomer(customer())
.setItems(items())
.setStatus(newStatus)
.setCreatedAt(createdAt())
.setUpdatedAt(Instant.now())
.setShippingAddress(shippingAddress())
.setBillingAddress(billingAddress())
.setTotalAmount(totalAmount())
.setTaxAmount(taxAmount())
.setShippingCost(shippingCost())
.build();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setOrderId(String orderId);
public abstract Builder setCustomer(Customer customer);
public abstract Builder setItems(List<OrderItem> items);
public abstract Builder setStatus(OrderStatus status);
public abstract Builder setCreatedAt(Instant createdAt);
public abstract Builder setUpdatedAt(Instant updatedAt);
public abstract Builder setShippingAddress(Address shippingAddress);
public abstract Builder setBillingAddress(Address billingAddress);
public abstract Builder setTotalAmount(BigDecimal totalAmount);
public abstract Builder setTaxAmount(BigDecimal taxAmount);
public abstract Builder setShippingCost(BigDecimal shippingCost);
public abstract Order build();
}
public enum OrderStatus {
PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED
}
}
@AutoValue
public abstract class Customer {
public abstract String customerId();
public abstract String name();
public abstract String email();
public abstract String phone();
public static Customer create(String customerId, String name, String email, String phone) {
return new AutoValue_Customer(customerId, name, email, phone);
}
public static Builder builder() {
return new AutoValue_Customer.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setCustomerId(String customerId);
public abstract Builder setName(String name);
public abstract Builder setEmail(String email);
public abstract Builder setPhone(String phone);
public abstract Customer build();
}
}
@AutoValue
public abstract class OrderItem {
public abstract String productId();
public abstract String productName();
public abstract int quantity();
public abstract BigDecimal unitPrice();
public abstract BigDecimal discount();
public BigDecimal totalPrice() {
return unitPrice().multiply(BigDecimal.valueOf(quantity())).subtract(discount());
}
public static OrderItem create(String productId, String productName, int quantity,
BigDecimal unitPrice, BigDecimal discount) {
return new AutoValue_OrderItem(productId, productName, quantity, unitPrice, discount);
}
public static Builder builder() {
return new AutoValue_OrderItem.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setProductId(String productId);
public abstract Builder setProductName(String productName);
public abstract Builder setQuantity(int quantity);
public abstract Builder setUnitPrice(BigDecimal unitPrice);
public abstract Builder setDiscount(BigDecimal discount);
public abstract OrderItem build();
}
}
Configuration Management
import java.time.Duration;
import java.util.Map;
@AutoValue
public abstract class ApplicationConfig {
public abstract DatabaseConfig database();
public abstract CacheConfig cache();
public abstract SecurityConfig security();
public abstract LoggingConfig logging();
public abstract Map<String, String> features();
public static ApplicationConfig create(DatabaseConfig database, CacheConfig cache,
SecurityConfig security, LoggingConfig logging,
Map<String, String> features) {
return new AutoValue_ApplicationConfig(database, cache, security, logging,
Map.copyOf(features));
}
public static Builder builder() {
return new AutoValue_ApplicationConfig.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setDatabase(DatabaseConfig database);
public abstract Builder setCache(CacheConfig cache);
public abstract Builder setSecurity(SecurityConfig security);
public abstract Builder setLogging(LoggingConfig logging);
public abstract Builder setFeatures(Map<String, String> features);
public abstract ApplicationConfig build();
}
}
@AutoValue
public abstract class DatabaseConfig {
public abstract String url();
public abstract String username();
public abstract String password();
public abstract int poolSize();
public abstract Duration connectionTimeout();
public static DatabaseConfig create(String url, String username, String password,
int poolSize, Duration connectionTimeout) {
return new AutoValue_DatabaseConfig(url, username, password, poolSize, connectionTimeout);
}
public static Builder builder() {
return new AutoValue_DatabaseConfig.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setUrl(String url);
public abstract Builder setUsername(String username);
public abstract Builder setPassword(String password);
public abstract Builder setPoolSize(int poolSize);
public abstract Builder setConnectionTimeout(Duration connectionTimeout);
public abstract DatabaseConfig build();
}
}
// Similar AutoValue classes for CacheConfig, SecurityConfig, LoggingConfig...
8. Testing AutoValue Classes
Unit Testing
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class AutoValueTest {
@Test
void testEqualsAndHashCode() {
Person person1 = Person.create("John Doe", 30, "[email protected]");
Person person2 = Person.create("John Doe", 30, "[email protected]");
Person person3 = Person.create("Jane Smith", 25, "[email protected]");
// Reflexivity
assertEquals(person1, person1);
// Symmetry
assertEquals(person1, person2);
assertEquals(person2, person1);
// Transitivity
assertNotEquals(person1, person3);
// Hash code consistency
assertEquals(person1.hashCode(), person2.hashCode());
assertNotEquals(person1.hashCode(), person3.hashCode());
// Null check
assertNotEquals(null, person1);
}
@Test
void testToString() {
Person person = Person.create("John Doe", 30, "[email protected]");
String toString = person.toString();
assertTrue(toString.contains("John Doe"));
assertTrue(toString.contains("30"));
assertTrue(toString.contains("[email protected]"));
}
@Test
void testBuilderValidation() {
// Test valid construction
AdvancedPerson validPerson = AdvancedPerson.builder()
.setName("Valid Name")
.setAge(25)
.setEmail("[email protected]")
.build();
assertNotNull(validPerson);
// Test invalid construction
assertThrows(IllegalStateException.class, () ->
AdvancedPerson.builder()
.setName("")
.setAge(25)
.setEmail("[email protected]")
.build()
);
}
@Test
void testCopyMethods() {
MutableLikePerson original = MutableLikePerson.create("John", 30, "[email protected]");
MutableLikePerson modified = original.withName("Jane").withAge(25);
assertEquals("Jane", modified.name());
assertEquals(25, modified.age());
assertEquals("[email protected]", modified.email());
// Original remains unchanged
assertEquals("John", original.name());
assertEquals(30, original.age());
}
@Test
void testCollectionImmutability() {
List<String> originalTeam = new ArrayList<>();
originalTeam.add("Alice");
originalTeam.add("Bob");
Project project = Project.builder()
.setName("Test Project")
.setTeamMembers(originalTeam)
.setTechnologies(Set.of("Java", "Spring"))
.setTaskEstimations(Map.of("task1", 5))
.build();
// Modify original list
originalTeam.add("Charlie");
// Project's team should not be affected
assertEquals(2, project.teamMembers().size());
assertThrows(UnsupportedOperationException.class,
() -> project.teamMembers().add("David"));
}
}
9. Best Practices and Performance
Performance Considerations
public class PerformanceBestPractices {
// ✅ Prefer static factory methods over builders for simple cases
@AutoValue
public abstract class EfficientPerson {
public abstract String name();
public abstract int age();
// Efficient for simple objects
public static EfficientPerson of(String name, int age) {
return new AutoValue_EfficientPerson(name, age);
}
}
// ✅ Use builders for complex objects with many optional fields
@AutoValue
public abstract class ComplexConfig {
public abstract String host();
public abstract int port();
public abstract Optional<String> username();
public abstract Optional<String> password();
public abstract Optional<Integer> timeout();
public abstract Optional<Boolean> sslEnabled();
public static Builder builder() {
return new AutoValue_ComplexConfig.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setHost(String host);
public abstract Builder setPort(int port);
public abstract Builder setUsername(Optional<String> username);
public abstract Builder setPassword(Optional<String> password);
public abstract Builder setTimeout(Optional<Integer> timeout);
public abstract Builder setSslEnabled(Optional<Boolean> sslEnabled);
public abstract ComplexConfig build();
}
}
// ✅ Cache expensive computations
@AutoValue
public abstract class CachedComputation {
public abstract double x();
public abstract double y();
private transient Double cachedDistance;
public double distanceFromOrigin() {
if (cachedDistance == null) {
cachedDistance = Math.sqrt(x() * x() + y() * y());
}
return cachedDistance;
}
public static CachedComputation of(double x, double y) {
return new AutoValue_CachedComputation(x, y);
}
}
}
// Memory efficiency with object pooling for frequently created objects
class PersonPool {
private static final Map<String, SoftReference<Person>> pool = new ConcurrentHashMap<>();
public static Person getOrCreate(String name, int age, String email) {
String key = name + "|" + age + "|" + email;
return pool.compute(key, (k, v) -> {
if (v != null && v.get() != null) {
return v;
}
Person person = Person.create(name, age, email);
return new SoftReference<>(person);
}).get();
}
}
Common Pitfalls and Solutions
public class CommonPitfalls {
// ❌ Don't expose mutable collections
@AutoValue
public abstract class ProblematicCollection {
public abstract List<String> getItems(); // Exposes mutable list
public static ProblematicCollection create(List<String> items) {
return new AutoValue_ProblematicCollection(items); // No defensive copy
}
}
// ✅ Do use immutable collections
@AutoValue
public abstract class SafeCollection {
public abstract List<String> getItems();
public static SafeCollection create(List<String> items) {
return new AutoValue_SafeCollection(List.copyOf(items)); // Defensive copy
}
}
// ❌ Don't forget validation in builders
@AutoValue
public abstract class NoValidation {
public abstract String email();
public static Builder builder() {
return new AutoValue_NoValidation.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setEmail(String email);
public abstract NoValidation build(); // No validation!
}
}
// ✅ Do add validation
@AutoValue
public abstract class WithValidation {
public abstract String email();
public static Builder builder() {
return new AutoValue_WithValidation.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");
public abstract Builder setEmail(String email);
abstract WithValidation autoBuild();
public WithValidation build() {
WithValidation obj = autoBuild();
if (!EMAIL_PATTERN.matcher(obj.email()).matches()) {
throw new IllegalStateException("Invalid email: " + obj.email());
}
return obj;
}
}
}
}
10. Migration from Manual Implementation
Before AutoValue
// Traditional JavaBean with manual implementation
public final class ManualPerson {
private final String name;
private final int age;
private final String email;
public ManualPerson(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
// Getters
public String getName() { return name; }
public int getAge() { return age; }
public String getEmail() { return email; }
// Manual equals implementation
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ManualPerson)) return false;
ManualPerson that = (ManualPerson) o;
return age == that.age &&
Objects.equals(name, that.name) &&
Objects.equals(email, that.email);
}
// Manual hashCode implementation
@Override
public int hashCode() {
return Objects.hash(name, age, email);
}
// Manual toString implementation
@Override
public String toString() {
return "ManualPerson{" +
"name='" + name + '\'' +
", age=" + age +
", email='" + email + '\'' +
'}';
}
}
After AutoValue
// Same functionality with AutoValue - much less code!
@AutoValue
public abstract class AutoValuePerson {
public abstract String name();
public abstract int age();
public abstract String email();
public static AutoValuePerson create(String name, int age, String email) {
return new AutoValue_AutoValuePerson(name, age, email);
}
}
Summary
AutoValue provides significant benefits for creating immutable value classes in Java:
Key Advantages:
- Reduced Boilerplate - Auto-generates
equals(),hashCode(),toString() - Immutability by Design - Prevents accidental modification
- Thread Safety - Immutable objects are inherently thread-safe
- Compile-time Safety - Errors caught at compile time, not runtime
- Performance - Generated code is highly optimized
Best Practices:
- Use builders for complex objects with many optional fields
- Always validate input in factory methods or builders
- Use defensive copying for collections
- Prefer static factory methods for simple objects
- Leverage copy methods for "modification" operations
- Integrate with serialization libraries when needed
AutoValue eliminates the tedium of writing and maintaining boilerplate code while ensuring correctness and performance, making it an excellent choice for immutable value classes in Java applications.