1El problema: Java te obliga a escribir mucho relleno

Antes de ver la herramienta, hay que entender qué dolor quita.

En Java, casi todas las clases que guardan datos (un conductor, un vehículo, una multa…) necesitan lo mismo: campos private, un método para leer cada campo (getter), otro para cambiarlo (setter), uno o varios constructores, un toString() para poder imprimirlo y un equals()/hashCode() para poder compararlo. Ese código se llama boilerplate: relleno repetitivo que no aporta nada nuevo pero que hay que escribir, leer y mantener.

Imagínatelo asíEs como rellenar a mano el mismo formulario de la autoescuela cada vez que alguien se matricula: nombre, DNI, dirección… Siempre lo mismo. Lombok es el sello de goma que lo estampa de golpe.

Mira la diferencia con una clase de tres campos:

Sin Lombok (≈ 60 líneas)

java
public class Conductor {
    private String dni;
    private String nombre;
    private int puntos;

    public Conductor() { }

    public Conductor(String dni, String nombre, int puntos) {
        this.dni = dni;
        this.nombre = nombre;
        this.puntos = puntos;
    }

    public String getDni() { return dni; }
    public void setDni(String dni) { this.dni = dni; }

    public String getNombre() { return nombre; }
    public void setNombre(String nombre) { this.nombre = nombre; }

    public int getPuntos() { return puntos; }
    public void setPuntos(int puntos) { this.puntos = puntos; }

    @Override
    public String toString() {
        return "Conductor(dni=" + dni + ", nombre=" + nombre
             + ", puntos=" + puntos + ")";
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Conductor)) return false;
        Conductor c = (Conductor) o;
        return puntos == c.puntos
            && Objects.equals(dni, c.dni)
            && Objects.equals(nombre, c.nombre);
    }

    @Override
    public int hashCode() {
        return Objects.hash(dni, nombre, puntos);
    }
}

Con Lombok (≈ 10 líneas)

java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Conductor {
    private String dni;
    private String nombre;
    private int puntos;
}

Las dos clases hacen exactamente lo mismo. Puedes llamar a conductor.getNombre(), conductor.setPuntos(8), new Conductor("123A", "Ana", 12) o imprimirlo con System.out.println(conductor) igual que si lo hubieras escrito tú.

Y si mañana añades un campo email, con Lombok no tocas nada más: getter, setter, constructor y toString se actualizan solos. Sin Lombok, tendrías que acordarte de tocar cinco sitios (y alguno se te olvidaría).

2¿Cómo funciona por dentro? (sin magia negra)

Saber esto te ahorra muchos sustos cuando algo «no compila».

Las cosas que empiezan por @ se llaman anotaciones: son etiquetas que pegas encima de una clase, campo o método. Por sí solas no hacen nada; alguien tiene que leerlas.

Lombok es un procesador de anotaciones: un programa que se «cuela» en el compilador de Java (javac). Cuando compilas, Lombok lee tus etiquetas y añade el código que falta antes de que se genere el .class. Resultado:

Imagínatelo asíTú escribes la receta en versión corta («haz una tortilla»). Lombok es el pinche de cocina que, justo antes de servir, la convierte en la receta completa paso a paso. El comensal (la JVM) solo ve el plato terminado.
Truco para verloEn IntelliJ, abre la vista Structure (Alt+7) de una clase con @Data: aparecen todos los métodos generados aunque no estén en el texto. También puedes usar Refactor → Delombok para ver el código «largo» que genera.

3Instalarlo en un proyecto Maven / Spring Boot

Dos piezas: la dependencia y avisar al compilador.

Si creas el proyecto con Spring Initializr y marcas la dependencia Lombok, ya viene todo hecho. Si lo añades a mano, en el pom.xml:

xml
<!-- 1) La dependencia: solo hace falta al compilar -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided</scope>   <!-- en Spring Boot la versión la pone el padre -->
</dependency>

<!-- 2) Registrarlo como procesador de anotaciones (obligatorio desde JDK 23) -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <annotationProcessorPaths>
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>
¿Por qué el paso 2?Hasta Java 22 el compilador buscaba procesadores de anotaciones por su cuenta. Desde JDK 23 ya no lo hace por defecto, así que hay que decirle explícitamente «usa Lombok». Si te lo saltas, verás errores como cannot find symbol: method getNombre() aunque tengas @Data puesto. En el curso usamos Java 25, así que este paso es obligatorio. (Fuera de Spring Boot tendrás que poner también <version> en ambos sitios.)

El IDE también tiene que enterarse

Si el IDE no está configurado, el proyecto compila con Maven pero el editor te lo llena todo de rojo porque «no ve» los métodos generados.

4@Getter y @Setter: leer y cambiar campos

Las más básicas. Todo lo demás se construye sobre ellas.

Puedes ponerlas sobre la clase (afectan a todos los campos) o sobre un campo (solo a ese).

java
@Getter                       // getters para TODOS los campos
public class Vehiculo {
    private String matricula;
    @Setter private String color; // setter SOLO para color
    private boolean electrico;
}

// Lombok genera:
//   getMatricula(), getColor(), isElectrico()   ← boolean usa "is"
//   setColor(String)

5@ToString: que al imprimirlo se entienda algo

Sin él, imprimir un objeto da algo como Conductor@1b6d3586.

java
@ToString
@AllArgsConstructor
public class Usuario {
    private String login;
    @ToString.Exclude private String password;  // ¡nunca al log!
}

System.out.println(new Usuario("ana", "1234"));
// → Usuario(login=ana)

Usa @ToString.Exclude para dos cosas: datos sensibles (contraseñas, tokens) y relaciones entre objetos que se apuntan mutuamente (lo verás en las trampas, sección 14).

6@EqualsAndHashCode: cuándo dos objetos son «el mismo»

Importante en cuanto metes objetos en un Set o de clave en un Map.

En Java, == compara si son el mismo objeto en memoria. equals() compara si son iguales en contenido. Por defecto (sin sobrescribirlo), equals() se comporta como ==, y eso casi nunca es lo que quieres: dos Matricula("1234ABC") creadas por separado deberían considerarse iguales.

hashCode() es un número «resumen» del objeto que usan HashSet y HashMap para encontrarlo rápido. La regla de oro: si dos objetos son equals, deben tener el mismo hashCode. Por eso siempre van juntos, y por eso Lombok los genera a la vez.

java
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Conductor {
    @EqualsAndHashCode.Include private String dni;  // solo el DNI decide
    private String nombre;
    private int puntos;
}
// Dos conductores con el mismo DNI son iguales aunque tengan distintos puntos.

7Los tres constructores

Cada uno crea un constructor distinto. Se pueden combinar.

AnotaciónQué constructor generaPara qué se usa típicamente
@NoArgsConstructorUno vacío: new Conductor()Frameworks que crean el objeto vacío y luego lo rellenan (JPA/Hibernate, Jackson al leer JSON).
@AllArgsConstructorUno con todos los campos, en el orden en que están declaradosCrear objetos completos de una vez, tests.
@RequiredArgsConstructorUno solo con los campos obligatorios: los final sin inicializar y los marcados con @NonNullInyección de dependencias en Spring (lo usarás muchísimo).

El caso estrella: servicios de Spring

En Spring, un servicio necesita otros objetos (un repositorio, un validador…). La forma recomendada de recibirlos es por constructor, con campos final. @RequiredArgsConstructor te escribe ese constructor:

Sin Lombok

java
@Service
public class ExpedienteService {
    private final ExpedienteRepository repo;
    private final NotificacionService avisos;

    public ExpedienteService(ExpedienteRepository repo,
                             NotificacionService avisos) {
        this.repo = repo;
        this.avisos = avisos;
    }
}

Con Lombok

java
@Service
@RequiredArgsConstructor
public class ExpedienteService {
    private final ExpedienteRepository repo;
    private final NotificacionService avisos;
}

Spring ve un único constructor y le pasa automáticamente el repositorio y el servicio de avisos. Si mañana necesitas otra dependencia, añades un campo final y listo.

Ojo con los final@NoArgsConstructor no puede dar valor a campos final, así que da error de compilación si los hay. Existe @NoArgsConstructor(force = true), que los rellena con null/0/false, pero úsalo solo si sabes por qué.

8@Data: el «todo en uno»

La más famosa y, precisamente por eso, la que más se usa mal.

@Data es un atajo que equivale a poner a la vez:

java
@Getter                  // en todos los campos
@Setter                  // en todos los campos NO final
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor

Perfecta para clases que solo transportan datos y cambian: un formulario, un objeto que rellenas por partes, un DTO mutable…

Tres detalles que pillan a todo el mundo
  1. @Data no genera constructor vacío ni «con todo». Si no hay campos final, el constructor que genera no tiene parámetros; si los hay, solo recibe esos. Por eso es tan habitual verlo junto a @NoArgsConstructor y @AllArgsConstructor.
  2. Si tú escribes cualquier constructor a mano, @Data deja de generar el suyo.
  3. Mete setters en todo. Si hay datos que no deberían cambiar nunca (un DNI, un id), @Data te los deja cambiar. Piensa si de verdad quieres eso.

9@Value y su primo moderno, el record

Para datos que, una vez creados, no cambian (inmutables).

@Value es la versión «de solo lectura» de @Data: todos los campos pasan a ser private final, la clase se vuelve final, genera getters, toString, equals/hashCode y un constructor con todos los campos, y no genera setters.

Con Lombok

java
@Value
public class Coordenadas {
    double latitud;
    double longitud;
}
// c.getLatitud()

Con Java puro (desde Java 16)

java
public record Coordenadas(double latitud,
                          double longitud) { }
// c.latitud()   ← sin "get"
¿Cuál uso?Hoy, para datos inmutables sencillos, prefiere record: es Java estándar, no necesita librerías y es justo lo que usa el dominio de MULTAGAL (Matricula, Coordenadas, Expediente…). Lombok sigue siendo muy útil para clases mutables (entidades JPA, objetos que se rellenan por partes), para los servicios de Spring (@RequiredArgsConstructor) y para el logger (@Slf4j). Comparativa completa al final, en Similitudes.

10@Builder: crear objetos con muchos campos sin volverse loco

Adiós a los new Multa(null, 200, null, "A-12", true, null).

Cuando un constructor tiene muchos parámetros, es muy fácil equivocarse de orden (¿el 3º era el importe o los puntos?). El patrón Builder te deja nombrar cada valor:

java
@Builder(toBuilder = true)
@Getter
public class Multa {
    private String matricula;
    private double importe;
    private int puntos;
    @Builder.Default private boolean pagada = false;
}

Multa m = Multa.builder()
        .matricula("1234ABC")
        .importe(200)
        .puntos(4)
        .build();          // pagada = false (gracias a @Builder.Default)

Multa pagada = m.toBuilder().pagada(true).build(); // copia con pagada = true

11@Slf4j: un logger gratis

Para escribir mensajes de log en vez de System.out.println.

java
@Slf4j
@Service
public class RadarService {
    public void procesar(Denuncia d) {
        log.info("Procesando denuncia {}", d.id());
        log.debug("Detalle: {}", d);
    }
}

// Lombok añade por ti:
// private static final org.slf4j.Logger log =
//         org.slf4j.LoggerFactory.getLogger(RadarService.class);

La variable siempre se llama log. Las llaves {} se sustituyen por los argumentos, en orden. Spring Boot ya trae SLF4J, así que funciona sin añadir nada.

12Otras que te vas a cruzar

No son las del día a día, pero conviene reconocerlas.

AnotaciónQué hace, en cristiano
@NonNullSobre un campo o parámetro: si alguien pasa null, lanza NullPointerException en el momento, en vez de fallar diez líneas después. En campos, además, los hace «obligatorios» para @RequiredArgsConstructor.
@WithGenera withPuntos(8): devuelve una copia del objeto con ese campo cambiado. Para objetos inmutables.
@Getter(lazy = true)El valor se calcula la primera vez que se pide y luego se reutiliza. Útil si calcularlo es caro.
@CleanupCierra un recurso (fichero, conexión) al acabar el bloque. Hoy es mejor el try-with-resources de Java.
@SneakyThrowsPermite lanzar excepciones comprobadas sin declararlas. Esconde errores: evítala en clase.
@SuperBuilderComo @Builder, pero funciona con herencia (el builder del hijo incluye los campos del padre).

13Recetas: qué poner según el tipo de clase

Combinaciones que funcionan bien en un proyecto Spring Boot.

Tipo de claseAnotaciones recomendadas
Servicio / controlador de Spring@RequiredArgsConstructor + campos private final (y @Slf4j si registra cosas)
Dato inmutable sencillo (DTO, valor)record de Java, sin Lombok
Objeto mutable «de datos» (formulario, config)@Data (+ @NoArgsConstructor / @AllArgsConstructor si los necesitas)
Entidad JPA (@Entity)@Getter @Setter @NoArgsConstructor y un @ToString que excluya relaciones. Nada de @Data (ver trampas).
Objeto con muchos campos opcionales@Builder (+ @AllArgsConstructor y @NoArgsConstructor si también lo usa un framework)
java
// Entidad JPA "segura" con Lombok
@Entity
@Getter @Setter
@NoArgsConstructor
@ToString
public class Conductor {
    @Id @GeneratedValue
    private Long id;
    private String dni;
    private String nombre;

    @ToString.Exclude
    @OneToMany(mappedBy = "conductor")
    private List<Multa> multas = new ArrayList<>();
}

14Trampas típicas (y cómo salir de ellas)

Lo que suele costar una tarde entera de depuración.

1. «cannot find symbol: getNombre()» aunque tengo @Data

Lombok no se está ejecutando al compilar. Casi siempre es que falta el bloque annotationProcessorPaths del pom.xml (sección 3), sobre todo con Java 23 o superior. Si solo falla en el editor pero ./mvnw compile va bien, es el IDE: activa el procesamiento de anotaciones.

2. StackOverflowError al imprimir o al meter en un Set

Si un Conductor tiene una lista de Multa y cada Multa apunta a su Conductor, el toString() del conductor imprime sus multas, que imprimen su conductor, que imprime sus multas… hasta reventar. Con equals/hashCode pasa lo mismo. Solución: excluye la relación en al menos un lado con @ToString.Exclude y @EqualsAndHashCode.Exclude.

3. @Data en entidades JPA

Además del bucle anterior, en JPA el equals/hashCode que usa todos los campos da problemas: el id cambia de null a un número al guardar (el objeto «cambia de identidad» dentro de un HashSet), y leer relaciones perezosas (lazy) desde toString puede lanzar consultas extra o LazyInitializationException. Por eso la recomendación habitual es @Getter @Setter @NoArgsConstructor y escribir equals/hashCode con cuidado (o no sobrescribirlos).

4. @Builder junto a @NoArgsConstructor no compila

@Builder necesita un constructor con todos los campos. Solo lo crea él mismo si tú no has declarado otro constructor. En cuanto añades @NoArgsConstructor, deja de hacerlo. Solución: añade también @AllArgsConstructor.

5. Mi valor por defecto desaparece con el builder

private boolean pagada = false; se ignora al usar builder(). Añade @Builder.Default encima del campo.

Regla prácticaSi no sabes qué hace una anotación de Lombok en una clase concreta, no la pongas. Es mejor escribir un getter de más que heredar un equals que no entiendes.

15Chuleta para el examen

Una línea por anotación.

AnotaciónGenera
@GettergetX() (o isX() si es boolean)
@SettersetX(valor) para campos no final
@ToStringtoString() legible con los campos
@EqualsAndHashCodeequals() + hashCode() a partir de los campos
@NoArgsConstructorConstructor vacío
@AllArgsConstructorConstructor con todos los campos
@RequiredArgsConstructorConstructor con los final y @NonNull
@DataGetter + Setter + ToString + EqualsAndHashCode + RequiredArgsConstructor
@ValueComo @Data pero inmutable (sin setters, todo final, constructor con todo)
@BuilderClase.builder().campo(v).build()
@Slf4jCampo log para registrar mensajes
@NonNullComprobación de null automática

16Ponte a prueba

Piensa la respuesta antes de abrirla.

¿Lombok hace falta cuando el programa ya se está ejecutando?

No. Trabaja solo durante la compilación: cuando se ejecuta, el código generado ya está dentro de los .class. Por eso su scope en Maven es provided.

Tengo una clase con @Data y dos campos no final. ¿Puedo hacer new Clase("a", "b")?

No. @Data incluye @RequiredArgsConstructor, que solo recibe campos final o @NonNull; aquí no hay ninguno, así que el constructor generado es vacío. Necesitas añadir @AllArgsConstructor.

¿Qué anotación usarías en un @Service con dos dependencias private final?

@RequiredArgsConstructor: genera el constructor con esas dos dependencias y Spring las inyecta por él.

¿Cómo se llama el getter que genera @Getter para private boolean activo;?

isActivo().

Mi proyecto usa Java 25 y todo sale en rojo: «cannot find symbol». ¿Qué reviso primero?

Que Lombok esté registrado en annotationProcessorPaths del maven-compiler-plugin (obligatorio desde JDK 23) y que el IDE tenga activado el procesamiento de anotaciones.

¿@Value o record para Matricula?

record: es inmutable, estándar de Java y no depende de ninguna librería. @Value tiene sentido en proyectos antiguos (antes de Java 16) o si necesitas algo que el record no permite, como heredar de otra clase.

≈Similitudes: cosas de Java que ya conoces (o conocerás) y se parecen

Para situar Lombok en el mapa: qué más resuelve el mismo problema y en qué se diferencia.

1. record: la alternativa oficial de Java a @Value (y a medio @Data)

Desde Java 16, un record es una clase especial pensada para guardar datos que no cambian. Con una sola línea, Java genera por ti el constructor con todos los campos, un método de lectura por campo, toString(), equals() y hashCode(). Es decir: casi lo mismo que @Value, pero sin ninguna librería.

Lombok

java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Conductor {
    private String dni;
    private String nombre;
    private int puntos;
}

c.getNombre();
c.setPuntos(8);     // se puede cambiar

record

java
public record Conductor(String dni,
                        String nombre,
                        int puntos) {
    // Validación opcional en el "constructor compacto"
    public Conductor {
        if (puntos < 0) throw new IllegalArgumentException();
    }
}

c.nombre();         // sin "get"
c.withPuntos(8);    // ✗ no existe: hay que crear otro
new Conductor(c.dni(), c.nombre(), 8);
recordLombok (@Data / @Value)
¿Hace falta instalar algo?✅ No, es Java puro❌ Dependencia + configurar compilador e IDE
¿Se pueden cambiar los datos?❌ Nunca (inmutable)✅ Con @Data sí; con @Value no
Nombre de los gettersnombre()getNombre() (convención JavaBean)
Constructor vacío❌ No puede tenerlo✅ @NoArgsConstructor
Herencia❌ No puede heredar de otra clase ni ser heredado✅ Una clase normal puede heredar
Builder, with…, logger❌ Hay que escribirlos a mano✅ @Builder, @With, @Slf4j
Validar datos al crear✅ Constructor compacto, muy cómodo🟡 Posible, pero escribiendo tú el constructor
¿Sirve como entidad JPA (@Entity)?❌ No (JPA necesita constructor vacío y campos modificables)✅ Sí, con @Getter @Setter @NoArgsConstructor
¿Sirve como DTO para JSON (Jackson / Spring)?✅ Sí, funciona directamente✅ Sí
¿Lo entiende cualquiera que sepa Java?✅ Sí🟡 Hay que conocer qué genera cada anotación
En cristiano¿Datos que se crean una vez y no cambian (una matrícula, unas coordenadas, la respuesta de una API)? → record. ¿Objetos que un framework necesita crear vacíos y rellenar (entidades JPA), que cambian con el tiempo o que heredan? → clase normal + Lombok. Por eso en MULTAGAL el dominio va con record y los servicios con @RequiredArgsConstructor.

2. El generador de código del IDE (Alt+Insert en IntelliJ)

IntelliJ, Eclipse y VS Code tienen un menú Generate que escribe getters, setters, constructores, toString, equals y hashCode con un par de clics. El resultado final es el mismo que con Lombok, pero con una diferencia clave:

Consejo de profe: escribe a mano o con el IDE una clase completa al menos una vez. Si entiendes lo que Lombok te ahorra, sabrás arreglarlo cuando falle.

3. @Override y los métodos de Object

Ya has usado anotaciones: @Override es una. La diferencia es que @Override solo comprueba (el compilador se queja si el método no sobrescribe nada), mientras que las de Lombok generan código.

Y lo que genera no es nada nuevo: toda clase de Java hereda de Object los métodos toString(), equals() y hashCode(). @ToString y @EqualsAndHashCode simplemente los sobrescriben por ti, igual que harías tú con @Override.

4. @Autowired en Spring frente a @RequiredArgsConstructor

En tutoriales antiguos verás las dependencias inyectadas así: @Autowired private ExpedienteRepository repo;. Funciona, pero el campo no puede ser final y la clase es difícil de probar sin Spring. La práctica recomendada hoy es la inyección por constructor, y @RequiredArgsConstructor la deja igual de corta que @Autowired pero con campos final. Mismo objetivo, forma más segura.

★Fuentes

← Todas las explicaciones «en cristiano» de AD · Volver a los temas de AD