Java

10 abr, 2026

Você não precisa mais de Lombok

Publicidade

Lombok foi uma salvação. Durante anos, nos poupou de escrever getters, setters, construtores e métodos repetitivos toStringequalsMas hashCodeo Java moderno alcançou esse nível. Entre registros, classes seladas, casamento de padrões e algumas outras adições, a maior parte do que Lombok fazia agora está integrada à linguagem.

Este artigo analisa cada anotação do Lombok que você provavelmente está usando, mostra o equivalente moderno em Java e explica por que você não precisa dela.

Por que reconsiderar Lombok?

Em essência, o Lombok é uma dependência adicional no seu projeto. E como qualquer dependência, aumenta a sua superfície de ataque. É mais uma coisa para auditar, corrigir e manter atualizada. Sempre sou cauteloso ao adicionar dependências quando não há necessidade real delas, e esse cuidado se torna ainda maior quando a própria linguagem passa a abranger o mesmo.

Além da questão da dependência, o Lombok traz consigo desvantagens que se acumulam ao longo do tempo:

  • Fragilidade dos plugins do compilador : o Lombok se integra a javaccomponentes internos. Cada versão principal do JDK apresenta riscos de quebra, e o ciclo de correções pode bloquear seu caminho de atualização.
  • Riscos de segurança e da cadeia de suprimentos : Cada dependência é uma vulnerabilidade em potencial. O Lombok funciona como um processador de anotações dentro do seu compilador e tem acesso profundo à sua compilação. Mesmo que o Lombok em si seja seguro hoje, ele representa mais um artefato na sua cadeia de suprimentos para monitorar e mais um ponto de entrada em caso de comprometimento. Se você estava por perto durante o período de festas de 2021, quando a vulnerabilidade do Log4j foi descoberta, sabe o quão problemático pode ser um patch urgente de dependência. Quanto menos dependências você tiver, menor será o impacto quando a próxima vulnerabilidade for revelada.
  • Lacunas no suporte da IDE : o processamento de anotações surpreende novos membros da equipe. A navegação no código, as ferramentas de refatoração e a análise estática nem sempre reconhecem o código gerado pelo Lombok.
  • Pontos cegos de depuração : os rastreamentos de pilha fazem referência a métodos gerados que você não pode acessar passo a passo ou ler no código-fonte.
  • Dependência de uma única biblioteca : Lombok é mantido por uma equipe pequena. Se o projeto desacelerar, seu código dependerá dele.

O Java moderno não apenas substitui o Lombok, mas o substitui por recursos de linguagem de primeira classe que todas as IDEs, depuradores e ferramentas entendem nativamente.

IDEs e IA já geram o código padrão.

Um dos principais atrativos iniciais de Lombok era a economia de digitação. Mas, em 2026, esse argumento perdeu grande parte da sua relevância.

As IDEs fazem isso há anos. IntelliJ IDEA, Eclipse e VS Code geram construtores, getters, setters equalshashCodebuilders toStringa partir de um menu ou atalho. O código gerado é Java puro: visível, pesquisável e depurável. Não há processador de anotações na sua compilação, nem com que se preocupar em relação à compatibilidade de plugins.

O desenvolvimento assistido por IA preenche a lacuna restante. GitHub Copilot, Gemini, Windsurf, Cursor, Claude Code e outros assistentes de codificação com IA geram código boilerplate tão rápido quanto você consegue descrevê-lo. Precisa de um construtor? Digite um comentário ou pergunte no chat. Precisa equalsde algo hashCodebaseado em campos específicos? A IA escreve em segundos, com todo o contexto da sua classe. Ao contrário do Lombok, o resultado é Java padrão que qualquer desenvolvedor pode ler sem precisar conhecer nenhuma biblioteca específica.

A combinação de registros (para tipos imutáveis), geração de IDE (para classes mutáveis) e assistência de IA (para qualquer coisa personalizada) cobre todos os cenários que o Lombok suporta, sem adicionar nenhuma dependência.

Lombok nos livrou da digitação. As ferramentas modernas também nos livram da digitação, mas sem ocultar o código.

Migração anotação por anotação

@Valor → Registros (substituição direta)

@ValueTorna uma classe imutável com getters, toStringequals, e hashCode. Registros são uma substituição direta, em uma única linha.

Lombok:

@Value
public class Product {
    String name;
    double price;
    String category;
}

Java moderno (16+):

public record Product(String name, double price, String category) {}

Essa única linha fornece:

  • Um construtor que recebe todos os campos.
  • Métodos de acesso ( name()price()category())
  • toString()equals(), e hashCode()com base em todos os campos
  • Imutabilidade por padrão: os campos sãofinal

Você pode adicionar validação em um construtor compacto:

public record Product(String name, double price, String category) {
    public Product {
        if (price < 0) throw new IllegalArgumentException("Price cannot be negative");
        name = name.strip();
    }
}

Se você estiver usando @Valuea versão atual, os registros são o caminho de atualização. Sem ressalvas.

@Data → Registros quando possível, classes simples quando não for possível

@DataGera getters, setters , toStringmétodos equalse propriedades hashCodeem uma classe mutável. Registros são imutáveis, portanto, não substituem @Dataem todos os casos. Mas, na prática, muitas @Dataclasses não precisam de mutabilidade. São DTOs, respostas, objetos de configuração ou objetos de valor que são criados uma única vez e nunca alterados. Para esses casos, um registro é a melhor escolha.

Se a classe for efetivamente imutável (criada uma única vez, com os campos nunca reatribuídos), mude para um registro:

// Before: @Data, but nothing ever calls the setters
@Data
public class Product {
    private String name;
    private double price;
    private String category;
}

// After: record, immutability is explicit
public record Product(String name, double price, String category) {}

Se a classe realmente precisar de mutabilidade , os registros não funcionarão. Isso inclui entidades JPA, construtores mutáveis ​​ou classes em que os campos são definidos após a construção. Para esses casos, descarte a mutabilidade @Datae use uma classe simples com acessadores gerados pela IDE.

public class Product {
    private String name;
    private double price;
    private String category;

    public Product(String name, double price, String category) {
        this.name = name;
        this.price = price;
        this.category = category;
    }

    // IDE-generated getters and setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public double getPrice() { return price; }
    public void setPrice(double price) { this.price = price; }
    public String getCategory() { return category; }
    public void setCategory(String category) { this.category = category; }
}

Sim, há mais linhas de código. Mas você controla a mutabilidade explicitamente, seus métodos setters podem incluir validação e não há processador de anotações em sua compilação.

Os registros também possuem algumas restrições estruturais que vale a pena conhecer:

  • Os registros são finais. Eles não podem ser estendidos por subclasses. Se sua @Dataclasse estiver em uma hierarquia de herança, um registro não funcionará.
  • Registros não podem estender outras classes (eles estendem implicitamente java.lang.Record). Eles podem implementar interfaces.
  • Os acessadores de registros usam `get` name(), não `get` getName(). Algumas estruturas e bibliotecas esperam métodos no estilo JavaBean getXxx(). A maioria das estruturas modernas (Jackson, Spring) lida bem com registros, mas verifique se você está integrando com bibliotecas mais antigas.

Uma regra prática: se você puder criar um registro, crie um registro. Se não puder, escreva uma classe simples. De qualquer forma, Lombok não é necessário.

@Getter e @Setter → Registros ou acessadores simples

Para dados imutáveis, os registros lidam com isso automaticamente. Para classes mutáveis, seu IDE gera acessadores em segundos, e o código fica visível para todos.

Lombok:

@Getter @Setter
public class UserPreferences {
    private String theme;
    private String language;
    private boolean notifications;
}

Java moderno:

Se o comportamento imutável funcionar (e geralmente funciona):

public record UserPreferences(String theme, String language, boolean notifications) {}

Se você precisa de mutabilidade, basta escrever a classe. As IDEs geram o código padrão instantaneamente, e você pode ler cada linha:

public class UserPreferences {
    private String theme;
    private String language;
    private boolean notifications;

    // IDE-generated getters and setters
    public String getTheme() { return theme; }
    public void setTheme(String theme) { this.theme = theme; }
    // ... remaining accessors
}

@ToString → Geração de registros ou IDE

Os registros geram um log de limpeza toString()automaticamente. Para classes que não são registros, use a geração automática do IDE ou sobrescreva manualmente. São apenas algumas linhas de código e você controla exatamente o que será registrado (importante quando os campos contêm dados sensíveis).

Lombok:

@ToString(exclude = "password")
public class User {
    private String name;
    private String email;
    private String password;
}

Java moderno:

public record User(String name, String email, String password) {
    @Override
    public String toString() {
        return "User[name=%s, email=%s]".formatted(name, email);
    }
}

O uso explícito toStringtem uma vantagem: você vê imediatamente quais campos estão incluídos. Sem mágica de anotações para esconder o que passwordestá sendo excluído.

@EqualsAndHashCode → Registros

Os registros são gerados equals()com hashCode()base em todos os componentes. Se você precisar de igualdade personalizada, substitua-a explicitamente.

Lombok:

@EqualsAndHashCode(of = {"id"})
public class Order {
    private Long id;
    private String description;
    private BigDecimal total;
}

Java moderno:

public record Order(Long id, String description, BigDecimal total) {
    @Override
    public boolean equals(Object o) {
        return o instanceof Order other && Objects.equals(id, other.id);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(id);
    }
}

@AllArgsConstructor, @NoArgsConstructor, @RequiredArgsConstructor → Registros e construtores flexíveis

Os registros vêm com um construtor canônico. Para classes que precisam de vários formatos de construtor, o Java 25 introduziu corpos de construtor flexíveis, para que você possa validar e definir campos antes de chamar o método super().

Lombok:

@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor
public class Connection {
    private final String host;
    private final int port;
    private String label;
}

Java moderno:

public record Connection(String host, int port, String label) {
    public Connection(String host, int port) {
        this(host, port, host + ":" + port);
    }
}

Para classes que não são registros, os construtores flexíveis do Java 25 permitem que você valide antes da delegação:

public class Connection {
    private final String host;
    private final int port;

    public Connection(String host, int port) {
        // Validate before assigning (Java 25+)
        if (host == null || host.isBlank())
            throw new IllegalArgumentException("Host required");
        this.host = host;
        this.port = port;
    }
}

@Builder → Construtor manual ou fábricas estáticas

Esta é a única anotação do Lombok para a qual o Java moderno não possui uma substituição direta em uma única linha. Mas as alternativas são claras e, muitas vezes, melhores.

Lombok:

@Builder
public class HttpRequest {
    private String url;
    private String method;
    private Map<String, String> headers;
    private String body;
    private Duration timeout;
}

Métodos de fábrica estáticos

Para tipos com alguns campos opcionais, as fábricas estáticas são mais simples do que um construtor:

public record HttpRequest(
    String url,
    String method,
    Map<String, String> headers,
    String body,
    Duration timeout
) {
    public static HttpRequest get(String url) {
        return new HttpRequest(url, "GET", Map.of(), null, Duration.ofSeconds(30));
    }

    public static HttpRequest post(String url, String body) {
        return new HttpRequest(url, "POST", Map.of(), body, Duration.ofSeconds(30));
    }
}

Construtor explícito

Para tipos com muitos campos opcionais, escreva o construtor. É mais código, mas cada linha é visível e depurável:

public record HttpRequest(
    String url,
    String method,
    Map<String, String> headers,
    String body,
    Duration timeout
) {
    public static Builder builder(String url) {
        return new Builder(url);
    }

    public static class Builder {
        private final String url;
        private String method = "GET";
        private Map<String, String> headers = Map.of();
        private String body;
        private Duration timeout = Duration.ofSeconds(30);

        Builder(String url) { this.url = url; }

        public Builder method(String method) { this.method = method; return this; }
        public Builder headers(Map<String, String> headers) { this.headers = headers; return this; }
        public Builder body(String body) { this.body = body; return this; }
        public Builder timeout(Duration timeout) { this.timeout = timeout; return this; }

        public HttpRequest build() {
            return new HttpRequest(url, method, headers, body, timeout);
        }
    }
}

Uso:

var request = HttpRequest.builder("https://api.example.com/data")
    .method("POST")
    .body("{\"key\": \"value\"}")
    .timeout(Duration.ofSeconds(10))
    .build();

Sim, são mais linhas. Mas você pode percorrer cada chamada de método passo a passo, seu IDE indexa todos os campos e nenhum processador de anotações está envolvido.

@Slf4j e @Log → System.getLogger ou declaração direta

Lombok:

@Slf4j
public class OrderService {
    public void process(Order order) {
        log.info("Processing order: {}", order.id());
    }
}

Java moderno (9+) com System.getLogger:

public class OrderService {
    private static final System.Logger LOG = System.getLogger(OrderService.class.getName());

    public void process(Order order) {
        LOG.log(System.Logger.Level.INFO, "Processing order: " + order.id());
    }
}

Ou diretamente com SLF4J (mais comum):

public class OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderService.class);

    public void process(Order order) {
        log.info("Processing order: {}", order.id());
    }
}

Uma única linha de declaração. Era só isso que Lombok estava te poupando.

@Limpeza → tente-com-recursos

Essa funcionalidade tornou-se desnecessária desde o Java 7.

Lombok:

public void readFile(String path) throws IOException {
    @Cleanup InputStream in = new FileInputStream(path);
    // use in
}

Java moderno (7+):

public void readFile(String path) throws IOException {
    try (var in = new FileInputStream(path)) {
        // use in
    }
}

try-with-resources é idiomático, universalmente compreendido e lida com múltiplos recursos de forma adequada.

@NonNull → Validação Jakarta, Objects.requireNonNull ou registros

Lombok:

public class UserService {
    public User createUser(@NonNull String name, @NonNull String email) {
        // Lombok inserts null check
        return new User(name, email);
    }
}

Java moderno com validação Jakarta (Spring Boot):

Se você estiver usando o Spring Boot, já terá o Jakarta Bean Validation no classpath. Adicione-o @NotNullaos parâmetros do seu método e anote a classe com @Validated:

@Validated
@Service
public class UserService {
    public User createUser(@NotNull String name, @NotNull String email) {
        return new User(name, email);
    }
}

O Spring intercepta a chamada e lança uma exceção ConstraintViolationExceptionse algum dos parâmetros for nulo. Não são necessárias verificações manuais.

O Spring Framework 7 (Spring Boot 4) leva isso adiante com a segurança contra nulos baseada em JSpecify, integrada ao framework. As anotações `@null` @NonNull@Nullable`@null` do JSpecify são reconhecidas pelo framework, IDEs e ferramentas de análise estática sem necessidade de configuração adicional.

Java puro, sem Spring:

public class UserService {
    public User createUser(String name, String email) {
        Objects.requireNonNull(name, "name must not be null");
        Objects.requireNonNull(email, "email must not be null");
        return new User(name, email);
    }
}

Os registros também rejeitam componentes nulos por padrão se você adicionar uma verificação de construtor compacto:

public record User(String name, String email) {
    public User {
        Objects.requireNonNull(name, "name");
        Objects.requireNonNull(email, "email");
    }
}

@SneakyThrows → lide com suas exceções

Isso sempre foi controverso. @SneakyThrowsIgnora exceções verificadas enganando o compilador.

Lombok:

@SneakyThrows
public String readConfig() {
    return Files.readString(Path.of("config.yml"));
}

Java moderno, simplesmente lide com isso:

public String readConfig() throws IOException {
    return Files.readString(Path.of("config.yml"));
}

Ou encapsule-o quando a interface não permitir exceções verificadas:

public String readConfig() {
    try {
        return Files.readString(Path.of("config.yml"));
    } catch (IOException e) {
        throw new UncheckedIOException(e);
    }
}

As exceções verificadas existem por um motivo. Ocultá-las dificulta a depuração.

val → var

Lombok introduziu valvariáveis ​​locais imutáveis ​​antes mesmo do Java var. Desde o Java 10, a inferência de tipo de variável local é nativa.

Lombok:

val items = List.of("a", "b", "c");
val count = items.size();

Java moderno (10+):

var items = List.of("a", "b", "c");
var count = items.size();

varÉ efetivamente final quando você não o reatribui. O compilador infere o tipo.

O perigo oculto: métodos setters @Data sem validação.

Isso é algo que não recebe a atenção necessária. Quando você adiciona @Datauma classe, o Lombok gera métodos setters públicos para cada campo. Esses métodos não realizam nenhuma validação. Eles apenas atribuem o valor. Isso significa que qualquer código, em qualquer lugar, pode colocar seu objeto em um estado inválido.

@Data
public class Customer {
    private String name;
    private String email;
    private int age;
}

// Anywhere in the codebase:
var customer = new Customer();
customer.setName("");        // empty name, valid?
customer.setEmail("not-an-email"); // no format check
customer.setAge(-5);         // negative age, accepted silently

O objeto existe. Está preenchido. É inválido. E nada o impediu de chegar a esse ponto.

Registros e construtores impõem a validade na criação.

Com um registro, você valida uma única vez, na construção, e o objeto tem sua validade garantida a partir desse ponto. Não há métodos setters para serem ignorados:

public record Customer(String name, String email, int age) {
    public Customer {
        if (name == null || name.isBlank())
            throw new IllegalArgumentException("Name is required");
        if (email == null || !email.contains("@"))
            throw new IllegalArgumentException("Valid email required");
        if (age < 0)
            throw new IllegalArgumentException("Age cannot be negative");
    }
}

Você ainda pode combinar isso com o Jakarta Bean Validation para verificações em nível de framework (Spring MVC, endpoints REST):

public record Customer(
    @NotBlank String name,
    @Email String email,
    @Min(0) int age
) {
    public Customer {
        Objects.requireNonNull(name, "name");
        Objects.requireNonNull(email, "email");
    }
}

Agora você tem o melhor dos dois mundos: imutabilidade em tempo de compilação, validação em tempo de execução na construção e validação do framework no limite da API. Nenhum método setter pode colocar o objeto em um estado inválido porque não existem métodos setter.

Para entidades JPA mutáveis ​​onde os métodos setters são inevitáveis, adicione lógica de validação dentro do setter em vez de deixá-lo como uma atribuição cega:

public void setAge(int age) {
    if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
    this.age = age;
}

Os setters gerados pelo Lombok não fazem isso. Se você escrever os seus próprios, você controla as regras.

E quanto às entidades JPA?

Essa é a objeção mais comum: “Registros não funcionam com JPA.” Isso é verdade. Entidades JPA precisam de um construtor sem argumentos, campos mutáveis ​​e getters/setters concretos.

Mas mesmo para entidades da JPA, o Lombok não é obrigatório:

@Entity
@Table(name = "products")
public class ProductEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private BigDecimal price;

    protected ProductEntity() {} // JPA requires this

    public ProductEntity(String name, BigDecimal price) {
        this.name = name;
        this.price = price;
    }

    public Long getId() { return id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public BigDecimal getPrice() { return price; }
    public void setPrice(BigDecimal price) { this.price = price; }
}

É mais código do que isso @Data? Sim. Mas:

  • Você controla exatamente o que é mutável e o que não é.
  • equalshashCodeuso de entidades JPA com Lombok é uma fonte conhecida de bugs (proxies, campos carregados sob demanda, entidades desanexadas).
  • Seu IDE gera isso em segundos. É um custo único.

Utilize registros para DTOs e objetos de valor que saem da camada de persistência:

// DTO returned from service layer: clean, immutable
public record ProductDTO(Long id, String name, BigDecimal price) {
    public static ProductDTO from(ProductEntity entity) {
        return new ProductDTO(entity.getId(), entity.getName(), entity.getPrice());
    }
}

Estratégia de migração

Você não precisa remover o Lombok de todo o seu código de uma só vez. Aqui está uma abordagem prática:

  1. Novo código : Pare de usar Lombok em novas classes. Use registros para armazenar dados e classes simples para estado mutável.
  2. Delombok gradualmente : Execute delombokem arquivos individuais para visualizar o código gerado e, em seguida, substitua por registros ou métodos gerados pela IDE.
  3. Comece com registros : Converta @Valueclasses @Dataque sejam portadoras de dados imutáveis. Essas são as soluções mais fáceis.
  4. Deixe as entidades JPA por último : elas são as que menos se beneficiam dos registros. Use a geração do IDE e siga em frente.
  5. Remova a dependência : Assim que não houver mais anotações do Lombok, remova-a lombokdo seu pom.xmlarquivo de configuração build.gradlee da configuração do processador de anotações.

Maven, remover:

<!-- Remove from dependencies -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided</scope>
</dependency>

<!-- Remove from annotation processor -->
<annotationProcessorPath>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</annotationProcessorPath>

Gradle, remover:

// Remove these lines
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

Tabela de referência rápida

Anotação de Lombok Substituição moderna do Java Desde
@Data record(se imutável) ou classe simples com acessadores gerados pela IDE Java 16
@Value record Java 16
@Getter/@Setter recordacessadores ou geração de IDE Java 16
@ToString recordou controle manual Java 16
@EqualsAndHashCode recordou controle manual Java 16
@AllArgsConstructor recordconstrutor canônico Java 16
@RequiredArgsConstructor recordou construtor explícito Java 16
@Builder Fábricas estáticas ou construtor explícito (a IDE pode gerar isso facilmente)
@Slf4j/@Log System.getLogger()ou declaração direta Java 9
@Cleanup try-com-recursos Java 7
@NonNull Jacarta @NotNull@ValidatedObjects.requireNonNull(), ou JSpecify Java 1.7
@SneakyThrows Tratamento adequado de exceções
val var Java 10

“Mas a equipe Spring ainda usa Lombok”

Sim, usam. O próprio código-fonte do Spring Framework utiliza o Lombok, assim como muitos dos guias e exemplos oficiais do Spring. Isso é um fato, e vale a pena entender o porquê antes de descartá-lo.

A base de código do Spring é enorme e antecede os registros em mais de uma década. Remover o Lombok de um projeto desse porte exigiria um esforço significativo de refatoração com benefícios práticos limitados. O código já funciona, a equipe está ciente disso e provavelmente está focada na próxima geração de novos recursos e melhorias para o framework.

Mas observe a direção, não o estado atual:

  • O Spring Data adota registros para projeções e DTOs desde o Spring Boot 3.x. A documentação recomenda ativamente o uso de registros para tipos de valores imutáveis.
  • A configuração automática do Spring Boot não requer o Lombok. Trata-se de uma conveniência para o desenvolvimento, não de uma dependência em tempo de execução.
  • O Spring AI , um dos projetos mais recentes do Spring, utiliza registros extensivamente em sua API.
  • A equipe do Spring afirmou repetidamente que o Lombok é uma escolha da equipe , não uma recomendação. Juergen Hoeller (líder do Spring Framework) observou que o próprio framework poderá eventualmente abandonar o Lombok à medida que o Java evolui.

Há também uma diferença prática entre a equipe do Spring usar o Lombok e você usar o Lombok. O Spring Framework é uma biblioteca utilizada por milhões de desenvolvedores, e seu estilo de código interno não afeta o seu classpath. O código da sua aplicação é diferente: o Lombok está presente no seu pipeline de build, na configuração da sua IDE e no processo de integração da sua equipe.

O fato da equipe do Spring usar Lombok em sua base de código não significa que você deva usá-lo na sua. Significa que eles ainda não migraram, e migrar um framework de 20 anos é um problema diferente de começar do zero ou evoluir um projeto menor.

Quando Lombok ainda faz sentido

Para ser justo, existem alguns cenários em que manter Lombok é razoável:

  • Grandes bases de código legadas, onde a remoção seria um esforço enorme com baixo retorno, são exemplos disso. O próprio Spring Framework é um bom exemplo. No entanto, se você tiver um bom conjunto de testes e tiver adotado o desenvolvimento assistido por IA, essa tarefa pode se tornar muito mais fácil, caso sua base de código não seja tão grande quanto o ecossistema do Spring Framework.

Minha recomendação pessoal: da próxima vez que você criar novas classes, opte por usar registros. Deixe as classes Java tradicionais apenas para entidades JPA. Comece não adicionando o Lombok ao seu novo código. Seja crítico: o fato de algo sempre ter sido feito de uma determinada maneira não significa que não haja necessidade de mudar e fazer as coisas de uma maneira melhor no futuro.

Lombok não é o inimigo. Ele resolveu problemas reais quando o Java não conseguia. Mas o Java agora consegue, e a linguagem continua melhorando a cada seis meses. A melhor dependência é aquela que você não precisa.

Referências

Boa programação!