This content originally appeared on DEV Community and was authored by Diego de Sousa Brandão
Introdução
A eterna busca por reduzir o código boilerplate em Java ganhou duas ferramentas poderosas: Java Records (introduzidos no Java 14 e estabilizados no Java 16) e Lombok (biblioteca amplamente adotada há anos). Embora ambos ataquem o mesmo problema – a verbosidade do Java – eles fazem isso de maneiras fundamentalmente diferentes, com implicações importantes para o design e arquitetura do seu código.
Este artigo vai além da comparação superficial de sintaxe e mergulha nas diferenças semânticas, casos de uso práticos e trade-offs de cada abordagem.
Records Java: Mais que Redução de Boilerplate
O que são Records?
Records não são apenas uma forma concisa de criar classes – eles são tipos de produto com semântica específica. Segundo a JEP 395:
“Records são portadores transparentes de dados imutáveis”
A palavra-chave aqui é transparente. Records seguem o princípio:
“A API de um record modela o estado, todo o estado, e nada além do estado”
Exemplo Básico
// Antes: 40+ linhas de código boilerplate
public class Pessoa {
private final String nome;
private final int idade;
public Pessoa(String nome, int idade) {
this.nome = nome;
this.idade = idade;
}
public String getNome() { return nome; }
public int getIdade() { return idade; }
@Override
public boolean equals(Object o) { /* implementação */ }
@Override
public int hashCode() { /* implementação */ }
@Override
public String toString() { /* implementação */ }
}
// Depois: 1 linha
public record Pessoa(String nome, int idade) {}
Características dos Records
-
Imutabilidade por Design: Todos os campos são
private final
- Transparência: Não há estado oculto
- Construtores Canônicos: Gerados automaticamente
-
Métodos Padrão:
equals()
,hashCode()
,toString()
implementados automaticamente - Acessores: Métodos com o mesmo nome dos componentes
Lombok: Flexibilidade Através de Anotações
O que é Lombok?
Lombok é uma biblioteca que gera código em tempo de compilação através de anotações. Não impõe semântica específica – é pura redução de boilerplate.
Exemplo com Lombok
@Data
public class Produto {
private String nome;
private double preco;
// Método personalizado
public void aplicarDesconto(double desconto) {
this.preco -= desconto;
}
}
Principais Anotações
-
@Data
: Gera getters, setters,equals()
,hashCode()
,toString()
-
@Value
: Classe imutável (equivalente a um record) -
@Builder
: Padrão Builder -
@Slf4j
: Injeção de logger -
@Getter/@Setter
: Getters e setters específicos
Comparação Detalhada
Aspecto | Records | Lombok |
---|---|---|
Semântica | Tipos de produto transparentes | Geração de código |
Imutabilidade | Por padrão | Opcional (@Value) |
Flexibilidade | Limitada | Alta |
Herança | Não suportada | Suportada |
Estado Oculto | Proibido | Permitido |
Dependência Externa | Não | Sim |
Versão Java | 14+ (estável no 16+) | Qualquer |
Desempenho | Nativo | Geração em tempo de compilação |
Quando Usar Records
Use Records para:
1. DTOs (Data Transfer Objects)
public record UsuarioDTO(String nome, String email, LocalDate nascimento) {}
2. Objetos de Valor
public record Dinheiro(BigDecimal valor, String moeda) {}
3. Respostas de API
public record ApiResponse<T>(T dados, int status, String mensagem) {}
4. Configurações Imutáveis
public record DatabaseConfig(String url, String usuario, int timeout) {}
5. Entidades JPA Simples (com limitações)
@Entity
public record Produto(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
Long id,
String nome,
BigDecimal preco
) {}
Evite Records quando:
- Precisar de mutabilidade
- Necessitar de herança
- Requerer estado oculto
- Trabalhar com frameworks que exigem construtores sem argumentos
- Precisar de métodos de ciclo de vida JPA complexos
Quando Usar Lombok
Use Lombok para:
1. Entidades JPA Complexas
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Usuario {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nome;
private String email;
@OneToMany(mappedBy = "usuario", cascade = CascadeType.ALL)
private List<Pedido> pedidos = new ArrayList<>();
}
2. Modelos de Negócio com Comportamento
@Data
@Slf4j
public class ContaBancaria {
private String numero;
private BigDecimal saldo;
public void sacar(BigDecimal valor) {
log.info("Sacando {} da conta {}", valor, numero);
if (saldo.compareTo(valor) >= 0) {
saldo = saldo.subtract(valor);
} else {
throw new IllegalArgumentException("Saldo insuficiente");
}
}
}
3. Builders Complexos
@Builder
@Data
public class Relatorio {
private String titulo;
private LocalDate dataInicio;
private LocalDate dataFim;
private List<String> colunas;
private Map<String, Object> parametros;
}
// Uso
Relatorio relatorio = Relatorio.builder()
.titulo("Vendas Mensais")
.dataInicio(LocalDate.now().minusMonths(1))
.dataFim(LocalDate.now())
.colunas(Arrays.asList("produto", "quantidade", "valor"))
.build();
Evite Lombok quando:
- O projeto exigir zero dependências externas
- Houver problemas de compatibilidade com IDEs
- A equipe não estiver familiarizada com a biblioteca
- Precisar de garantias semânticas específicas
Casos de Uso Práticos
Cenário 1: Sistema de E-commerce
// DTO para resposta da API - USE RECORD
public record ProdutoResponse(
Long id,
String nome,
BigDecimal preco,
String categoria
) {}
// Entidade JPA com comportamento - USE LOMBOK
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Produto {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nome;
private BigDecimal preco;
private String categoria;
private Integer estoque;
public void reduzirEstoque(int quantidade) {
if (estoque < quantidade) {
throw new IllegalArgumentException("Estoque insuficiente");
}
this.estoque -= quantidade;
}
}
Cenário 2: Sistema Bancário
// Objeto de valor imutável - USE RECORD
public record Moeda(String codigo, String nome, String simbolo) {}
// Entidade complexa com estado mutável - USE LOMBOK
@Entity
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@NoArgsConstructor
@Slf4j
public class Conta {
@Id
@EqualsAndHashCode.Include
private String numero;
private BigDecimal saldo;
private String titular;
@OneToMany(mappedBy = "conta", cascade = CascadeType.ALL)
private List<Transacao> transacoes = new ArrayList<>();
public void transferir(BigDecimal valor, Conta destino) {
log.info("Transferindo {} de {} para {}", valor, this.numero, destino.numero);
// Lógica de transferência
}
}
Híbrido: Usando Records e Lombok Juntos
// Record com anotações do Lombok para funcionalidades adicionais
@Slf4j
public record ProcessamentoResult(
String id,
LocalDateTime timestamp,
boolean sucesso,
String mensagem
) {
public ProcessamentoResult {
log.info("Resultado do processamento {}: {}", id, sucesso ? "SUCESSO" : "FALHA");
}
}
O Futuro: Spring Data Commons Recomenda Records
O Spring Framework oficialmente recomenda Records para DTOs e projeções:
// Recomendação oficial do Spring Data
public record NamesOnly(String firstname, String lastname) {}
// Em vez de usar Lombok
@Value
class NamesOnly {
String firstname, lastname;
}
Diretrizes para Tomada de Decisão
Escolha Records quando:
Estiver no Java 16+
Precisar de objetos imutáveis
Quiser garantias semânticas fortes
Criar DTOs ou objetos de valor
Buscar máxima performance (sem overhead de biblioteca)
Escolha Lombok quando:
Precisar de flexibilidade máxima
Trabalhar com herança
Requerer mutabilidade
Usar versões antigas do Java
Precisar de funcionalidades como @builder ou @Slf4j
Use Ambos quando:
Records para DTOs e objetos de valor
Lombok para entidades e modelos de negócio
Aproveitar o melhor de cada abordagem
Migração Gradual
Estratégia de Migração para Records:
- Identifique candidatos: DTOs, objetos de valor, classes imutáveis simples
- Migre gradualmente: Comece com classes menos críticas
- Teste thoroughly: Verifique serialização e frameworks
- Monitore performance: Records podem ter características diferentes
Exemplo de Migração:
// Antes (Lombok)
@Value
public class Endereco {
String rua;
String cidade;
String cep;
}
// Depois (Record)
public record Endereco(String rua, String cidade, String cep) {}
Considerações de Performance
Records:
Sem overhead de biblioteca
Otimizações do compilador
Criação de objetos em atualizações (imutabilidade)
Lombok:
Código gerado otimizado
Dependência de bibliotecas externas
Possível overhead de reflection em algumas situações
Conclusão
A escolha entre Records e Lombok não é uma questão de “melhor” ou “pior”, mas sim de adequação ao contexto:
- Records brilham quando você precisa de semântica forte, imutabilidade e transparência
- Lombok excele quando você precisa de flexibilidade, mutabilidade e compatibilidade com versões antigas
A tendência é usar ambos estrategicamente:
- Records para DTOs, objetos de valor e dados imutáveis
- Lombok para entidades complexas e modelos de negócio
Com o Java evoluindo e mais projetos adotando versões recentes, Records estão se tornando a escolha padrão para muitos casos de uso. No entanto, Lombok continuará relevante para cenários que demandam máxima flexibilidade.
A chave é entender que essas ferramentas resolvem problemas similares de maneiras diferentes, e a escolha certa depende das necessidades específicas do seu projeto, versão do Java e arquitetura da aplicação.
Referências Bibliográficas
- Replace Some Lombok Features with record in Java 17: A Modern Alternative
- https://github.com/spring-projects/spring-data-commons/commit/959bde4d37b10342d963cf47d0bbf1b68000ff6a
- https://github.com/spring-projects/spring-data-commons/pull/2794
- https://medium.com/@fabio.alvaro/records-ou-lombok-qual-a-melhor-escolha-para-o-seu-projeto-java-b0d1fde468f2
- https://javatechonline.com/java-records-vs-jpa-entities-and-lombok/
This content originally appeared on DEV Community and was authored by Diego de Sousa Brandão