Skip to main content
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).
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.

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.
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.

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.

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.

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.
Tired of Poor Java Skills? Fix That With Deep Grasping!Learn More