DEV Community

Cover image for Java Records Guide: Build Clean Data Classes in Java 14+ (With Examples)
Aarav Joshi
Aarav Joshi

Posted on

Java Records Guide: Build Clean Data Classes in Java 14+ (With Examples)

As a best-selling author, I invite you to explore my books on Amazon. Don't forget to follow me on Medium and show your support. Thank you! Your support means the world!

Java Records are a powerful feature introduced in Java 14 that simplify the creation of immutable data classes. They provide a concise way to declare classes that are meant to be simple carriers for immutable data.

Let's explore effective techniques for building robust data classes with records.

Basic Record Structure

Records automatically generate constructors, getters, equals(), hashCode(), and toString() methods. This eliminates common boilerplate code:

public record Employee(String id, String name, double salary) {}

// Usage
Employee emp = new Employee("E123", "John Doe", 75000.00);
String name = emp.name();  // Accessor method
Enter fullscreen mode Exit fullscreen mode

Compact Constructors

Compact constructors offer validation and normalization of record components without duplicating parameters:

public record Customer(String id, String email) {
    public Customer {
        if (id == null || email == null) {
            throw new NullPointerException("Id and email cannot be null");
        }
        email = email.toLowerCase().trim();
    }
}
Enter fullscreen mode Exit fullscreen mode

Generic Records

Generic records create reusable type-safe containers:

public record Result<T>(T value, String message) {
    public boolean isSuccess() {
        return value != null;
    }

    public static <T> Result<T> success(T value) {
        return new Result<>(value, "Success");
    }

    public static <T> Result<T> error(String message) {
        return new Result<>(null, message);
    }
}
Enter fullscreen mode Exit fullscreen mode

Interface Implementation

Records can implement interfaces to add behavior while maintaining immutability:

public interface Printable {
    String getFormattedString();
}

public record Invoice(String number, double amount) implements Printable {
    @Override
    public String getFormattedString() {
        return String.format("Invoice %s: $%.2f", number, amount);
    }
}
Enter fullscreen mode Exit fullscreen mode

Pattern Matching

Records work seamlessly with pattern matching in switch expressions:

public record Shape(String type, double area) {}

public String describeShape(Shape shape) {
    return switch (shape) {
        case Shape(var type, var area) when area > 100 ->
            "Large " + type;
        case Shape(var type, var area) ->
            "Small " + type;
    };
}
Enter fullscreen mode Exit fullscreen mode

Framework Integration

Spring Framework Integration:

@Configuration
public class Config {
    @Bean
    public DatabaseConfig databaseConfig() {
        return new DatabaseConfig("localhost", 5432, "mydb");
    }
}

public record DatabaseConfig(String host, int port, String database) {}
Enter fullscreen mode Exit fullscreen mode

Jackson Serialization:

public record UserDTO(
    @JsonProperty("user_id") String userId,
    @JsonProperty("full_name") String fullName
) {}
Enter fullscreen mode Exit fullscreen mode

Nested Records

Records can be composed to create complex data structures:

public record Address(String street, String city, String country) {}

public record User(String id, String name, Address address) {
    public String getFullAddress() {
        return String.format("%s, %s, %s", 
            address.street(), 
            address.city(), 
            address.country()
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Validation Patterns

Implement comprehensive validation using static factory methods:

public record Person(String name, int age) {
    private static final int MIN_AGE = 0;
    private static final int MAX_AGE = 150;

    public static Person create(String name, int age) {
        if (name == null || name.trim().isEmpty()) {
            throw new IllegalArgumentException("Name cannot be empty");
        }
        if (age < MIN_AGE || age > MAX_AGE) {
            throw new IllegalArgumentException("Invalid age range");
        }
        return new Person(name.trim(), age);
    }
}
Enter fullscreen mode Exit fullscreen mode

Immutable Collections

Use records with immutable collections for complete immutability:

public record Team(String name, List<String> members) {
    public Team {
        members = List.copyOf(members);  // Creates unmodifiable copy
    }

    public static Team of(String name, String... members) {
        return new Team(name, Arrays.asList(members));
    }
}
Enter fullscreen mode Exit fullscreen mode

Builder Pattern

Implement builder pattern for records with many fields:

public record Configuration(
    String host, 
    int port, 
    String username, 
    String password, 
    boolean ssl
) {
    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private String host = "localhost";
        private int port = 8080;
        private String username;
        private String password;
        private boolean ssl = false;

        public Builder host(String host) {
            this.host = host;
            return this;
        }

        public Builder port(int port) {
            this.port = port;
            return this;
        }

        // Additional builder methods

        public Configuration build() {
            return new Configuration(host, port, username, password, ssl);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Optional Fields

Handle optional values effectively:

public record Article(
    String title,
    String content,
    Optional<String> author
) {
    public Article(String title, String content) {
        this(title, content, Optional.empty());
    }

    public String getAuthorName() {
        return author.orElse("Anonymous");
    }
}
Enter fullscreen mode Exit fullscreen mode

Record Methods

Add utility methods to enhance functionality:

public record Point(int x, int y) {
    public double distanceTo(Point other) {
        return Math.sqrt(
            Math.pow(other.x - x, 2) + 
            Math.pow(other.y - y, 2)
        );
    }

    public Point translate(int dx, int dy) {
        return new Point(x + dx, y + dy);
    }
}
Enter fullscreen mode Exit fullscreen mode

Serialization

Implement custom serialization when needed:

public record SecureData(
    @JsonProperty("encrypted_data") 
    String encryptedData
) {
    private static final Cipher cipher = initializeCipher();

    public static SecureData encrypt(String data) {
        try {
            byte[] encrypted = cipher.doFinal(data.getBytes());
            return new SecureData(Base64.getEncoder().encodeToString(encrypted));
        } catch (Exception e) {
            throw new RuntimeException("Encryption failed", e);
        }
    }

    public String decrypt() {
        try {
            byte[] decoded = Base64.getDecoder().decode(encryptedData);
            return new String(cipher.doFinal(decoded));
        } catch (Exception e) {
            throw new RuntimeException("Decryption failed", e);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Records serve as excellent data carriers, providing immutability and reducing boilerplate code. Their integration with modern Java features and frameworks makes them ideal for building robust applications. The techniques presented here demonstrate their versatility in creating clean, maintainable code while ensuring type safety and immutability.

Remember to consider your specific use case when choosing among these patterns. Some situations might require additional validation, while others might benefit from simpler implementations. The key is to leverage records' built-in features while adding only the complexity that your application truly needs.


101 Books

101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.

Check out our book Golang Clean Code available on Amazon.

Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!

Our Creations

Be sure to check out our creations:

Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools


We are on Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva

Top comments (0)