Java Records in Functional Programming
Java isn’t traditionally a functional programming (FP) language, but with the introduction of Records in Java 14+ (stable since Java 16), it’s become easier to write FP-inspired, data-oriented code.
In this article, we’ll explore how Records support key functional programming principles like immutability, value-based semantics, and pure data modeling.
๐ฆ What Are Java Records?
Records are a concise way to declare immutable data carriers in Java. They auto-generate:
- Constructor
- Getters
equals()andhashCode()toString()
public record User(String name, int age) {}
This class is equivalent to a verbose POJO, but it’s immutable and much cleaner.
๐ง Why Records Fit Functional Programming
Functional programming promotes writing pure functions that avoid mutable state. Here’s how records align:
- ✅ Immutability – Record fields are
finaland cannot be changed after construction. - ✅ Value semantics – Two records with the same data are equal.
- ✅ Data as a value – Emphasizes transforming data over modifying it.
๐ Working with Records in Functional Style
1. Record Transformation with Functions
Create pure functions that return new records instead of modifying existing ones:
public record Product(String name, double price) {}
public Product applyDiscount(Product p, double percent) {
return new Product(p.name(), p.price() * (1 - percent / 100));
}
2. Using Streams with Records
List<Product> discounted = products.stream() .map(p -> applyDiscount(p, 10)) .toList();
Because records are immutable, these transformations are safe and side-effect-free.
๐ Records as Algebraic Data Types (ADTs)
In FP, data is often modeled with **Algebraic Data Types** like sum types (variants). Java doesn’t have native sum types, but you can mimic them using sealed interfaces + records:
sealed interface Payment permits Card, Cash {}
record Card(String cardNumber) implements Payment {}
record Cash(double amount) implements Payment {}
This pattern mirrors pattern matching and variant-based logic in languages like Scala and Haskell.
String handlePayment(Payment p) {
return switch (p) {
case Card c -> "Paid with card: " + c.cardNumber();
case Cash c -> "Paid in cash: " + c.amount();
};
}
๐งผ Best Practices
- ✅ Use records to model **pure data**, especially DTOs, events, and config objects.
- ✅ Favor **stateless transformations** over mutating logic.
- ❌ Avoid adding mutable fields or logic-heavy methods to records.
- ⚠️ Records are not suitable for ORM/JPA entities due to immutability and lack of no-arg constructor.
๐ Conclusion
Records + Functional Thinking = Cleaner, Safer Java
Java Records offer a powerful way to embrace functional programming principles, like immutability, pure functions, and value-based reasoning. They’re perfect for modeling data, applying transformations, and building more robust, testable code.
Combine records with Streams, Optionals, and pattern matching — and your Java starts feeling surprisingly functional!
Comments
Post a Comment