Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A Java generic record is an immutable, transparent data carrier that accepts one or more type parameters, allowing its state components to operate on arbitrary types while maintaining the boilerplate-reduction semantics of standard Java records. By combining the record keyword with generic type declarations, it enforces type safety at compile-time while automatically generating the canonical constructor, accessor methods, equals(), hashCode(), and toString() for the parameterized types.

Syntax and Declaration

Type parameters are declared in angle brackets <...> immediately following the record identifier and preceding the record header (the component list).
public record Pair<T, U>(T first, U second) {}
When instantiated, the compiler infers the type arguments (via the diamond operator) or accepts explicit type declarations, propagating these types to the implicitly generated canonical constructor and accessor methods.
Pair<String, Integer> pair = new Pair<>("Count", 42);
String f = pair.first();   // Return type is strictly String
Integer s = pair.second(); // Return type is strictly Integer

Bounded Type Parameters

Generic records fully support type bounding, restricting the types that can be passed as arguments. This is declared using the extends keyword for upper bounds.
public record NumericBounds<T extends Number>(T lower, T upper) {}
If a record component invokes methods on a generic type, bounding is required to guarantee those methods exist at compile-time.

Constructor Semantics

Generic type parameters are fully accessible within both canonical and compact constructors. In a compact constructor, the parameters are implicitly available, and you can perform validation against the generic types.
public record ValidatedWrapper<T>(T payload) {
    // Compact constructor
    public ValidatedWrapper {
        if (payload == null) {
            throw new NullPointerException("Payload of type T cannot be null");
        }
    }
}

Interface Implementation

While records cannot extend classes (as they implicitly extend java.lang.Record), generic records can implement interfaces. The record’s type parameters can be passed down to the generic interface it implements.
public record ComparableNode<T extends Comparable<T>>(T data) 
       implements Comparable<ComparableNode<T>> {
    
    @Override
    public int compareTo(ComparableNode<T> other) {
        return this.data.compareTo(other.data());
    }
}

Static Context Restrictions

Because type parameters in Java are tied to instance state, the static context of a generic record cannot reference the class-level type parameters. Static fields and static methods must declare their own independent type parameters if they require generic behavior.
public record Container<T>(T item) {
    
    // ILLEGAL: Cannot make a static reference to the non-static type T
    // public static T getDefault() { ... }

    // LEGAL: The static method declares its own independent type parameter <U>
    public static <U> Container<U> empty(U defaultItem) {
        return new Container<>(defaultItem);
    }
}

Type Erasure and Bytecode

Like all generics in Java, generic records are subject to type erasure. At compile-time, the Java compiler enforces type safety, but at runtime, the JVM strips the generic type information. For a record declared as record Box<T>(T value), the generated bytecode translates the component type T to its upper bound (which is Object if unbounded). The compiler automatically inserts the necessary type casts at the call site when accessor methods are invoked. Consequently, Box<String> and Box<Integer> share the exact same Class object at runtime.
Master Java with Deep Grasping Methodology!Learn More