Los record llegaron en Java 16 para simplificar la creación de clases cuyo objetivo principal es representar datos. Evitan tener que escribir constructores, getters, equals(), hashCode() y toString() manualmente.
Pero ¿son simplemente clases más cortas? No exactamente. Tienen algunas restricciones importantes, especialmente cuando hablamos de herencia.
Supongamos que queremos representar un usuario:
public record Usuario(String nombre, int edad) {
}
Java genera automáticamente:
- Un constructor con los componentes declarados.
- Los métodos de acceso nombre() y edad().
- Las implementaciones de equals(), hashCode() y toString().
Podemos utilizarlo así:
Usuario usuario = new Usuario("Ana", 30);
System.out.println(usuario.nombre());
System.out.println(usuario.edad());
System.out.println(usuario);
Observá que los métodos de acceso no siguen la convención tradicional de JavaBeans: se llaman nombre() y edad(), no getNombre() y getEdad().
Además, los componentes de un record son implícitamente final: no podemos reasignar sus valores después de construir la instancia.
¿Un record puede heredar de otra clase? No. Un record no puede extender una clase definida por nosotros.
En Java, los records heredan implícitamente de java.lang.Record. Por eso, esta declaración no es válida:
class Persona {
String nombre;
}
record Usuario(String nombre, int edad)
extends Persona { // Error de compilación
}
Tampoco podemos declarar explícitamente extends Record, porque esa herencia ya está definida por el lenguaje.
Esta restricción permite que los records tengan una estructura de datos bien definida y evita que se introduzca una jerarquía de clases que contradiga su modelo.
Por lo tanto, si necesitamos herencia de implementación entre clases, un record probablemente no sea la herramienta adecuada.
¿Un record puede implementar una interfaz? Sí. Un record puede implementar una o varias interfaces, igual que una clase convencional.
Por ejemplo:
interface Identificable {
Long id();
}
public record Usuario(Long id, String nombre)
implements Identificable {
}
El método id() exigido por la interfaz ya existe porque es un accesor generado automáticamente para el componente id.
También podemos implementar interfaces que requieran comportamiento:
interface Saludable {
boolean estaActivo();
}
public record Usuario(String nombre, boolean activo)
implements Saludable {
@Override
public boolean estaActivo() {
return activo;
}
}
Esto resulta especialmente útil cuando queremos aprovechar el polimorfismo sin necesitar herencia de clases.
¿Podemos agregar métodos a un record? Sí. Un record no se limita a almacenar datos: también puede contener lógica de negocio.
public record Rectangulo(double ancho, double alto) {
public double area() {
return ancho * alto;
}
}
Podemos utilizarlo así:
Rectangulo rectangulo = new Rectangulo(5, 3);
System.out.println(rectangulo.area()); // 15.0
También podemos declarar métodos estáticos, constructores adicionales y constructores compactos para validar los datos.
Por ejemplo:
public record Usuario(String nombre, int edad) {
public Usuario {
if (nombre == null || nombre.isBlank()) {
throw new IllegalArgumentException(
"El nombre es obligatorio"
);
}
if (edad < 0) {
throw new IllegalArgumentException(
"La edad no puede ser negativa"
);
}
}
}
El constructor compacto permite validar los componentes sin tener que escribir las asignaciones correspondientes: Java las realiza automáticamente después de ejecutar su cuerpo.
¿Podemos modificar los componentes de un record?
No podemos reasignar directamente sus componentes una vez construida la instancia:
Usuario usuario = new Usuario("Ana", 30);
usuario.nombre = "María"; // Error
Sin embargo, hay una distinción importante: que los componentes sean final no significa que todos los objetos referenciados sean inmutables.
public record Grupo(List<String> integrantes) {
}
En este caso no podemos reasignar integrantes, pero sí podríamos modificar la lista si es mutable:
Grupo grupo = new Grupo(new ArrayList<>());
grupo.integrantes().add("Ana");
Si queremos proteger la colección, podemos realizar una copia defensiva:
public record Grupo(List<String> integrantes) {
public Grupo {
integrantes = List.copyOf(integrantes);
}
}
Así evitamos que modificaciones posteriores sobre la lista original o sobre la lista devuelta alteren el contenido del grupo.
¿Podemos redefinir equals() y otros métodos?
Sí, aunque generalmente no es necesario. Java genera automáticamente equals(), hashCode() y toString() utilizando los componentes del record.
Podemos redefinir estos métodos si tenemos un motivo concreto. También podemos declarar constructores adicionales, siempre que respeten las reglas del lenguaje.
Lo que no podemos hacer es declarar instancias adicionales de campos de estado arbitrarios: los campos de instancia de un record corresponden a sus componentes. Sí podemos declarar campos estáticos.
¿Cuándo conviene usar records?
Los records son una buena opción para:
- DTOs utilizados en APIs REST.
- Valores que se pasan entre capas de una aplicación.
- Resultados de consultas.
- Representar coordenadas, importes u otros valores.
- Modelar datos que deben tener una estructura explícita y estable.
En cambio, una clase convencional puede ser más apropiada cuando necesitamos herencia de implementación, estado mutable o un ciclo de vida complejo.
Los records no son simplemente una forma abreviada de escribir clases. Son una característica del lenguaje diseñada para representar datos con una estructura explícita.
Podemos agregarles comportamiento e implementar interfaces, pero no podemos extender otras clases ni modificar la referencia de sus componentes.







