Conheça: "O inferno do construtor vazio" em Kotlin + JPA
\n\nSe você já tentou fazer o Kotlin funcionar com JPA (Java Persistence API), certamente já se deparou com a situação em que, ao iniciar a aplicação, o Hibernate lança uma exceção como:
\n\norg.hibernate.InstantiationException: No default constructor for entity: com.example.User\n\n\nFamiliar? Pois é. Este é o problema que acumulou milhares de visualizações no Stack Overflow e recebeu o nome sombrio de "Kotlin with JPA: default constructor hell".
\n\nNeste artigo, vamos analisar com calma, passo a passo:
\n- \n
- Por que esse erro ocorre \n
- Por que o Kotlin "não quer" gerar um construtor vazio \n
- Três formas comprovadas de resolver o problema (do simples ao profissional) \n
- Qual método escolher para o seu projeto \n
Vamos lá!
\n\nPor que o Hibernate exige um construtor sem parâmetros?
\n\nVamos começar com Java. No Java clássico, estamos acostumados a escrever:
\n\npublic class User {\n private Long id;\n private String name;\n\n // Construtor vazio obrigatório\n public User() {}\n\n // Getters, setters...\n}\n\n\nO Hibernate (e qualquer implementação JPA) usa reflexão para criar objetos. Quando ele lê uma linha do banco de dados, precisa criar uma instância da classe User e, em seguida, preencher os campos via reflexão. Para isso, ele precisa de um construtor sem parâmetros (default constructor).
Em Java, o compilador cria automaticamente um construtor vazio se você não definir nenhum outro. Portanto, o problema geralmente não ocorre.
\n\nE qual é a particularidade do Kotlin?
\n\nKotlin é uma linguagem com forte ênfase em imutabilidade e segurança. Veja uma classe Kotlin típica:
\n\ndata class User(\n val id: Long = 0,\n val name: String = ""\n)\n\n\nAqui ocorrem duas coisas importantes:
\n- \n
- Todas as propriedades são declaradas no construtor primário (primary constructor) \n
- As propriedades são declaradas como
val(imutáveis) e têm valores padrão \n
O Kotlin não gera um construtor vazio separado se você tiver um construtor primário com parâmetros. Mesmo que todos os parâmetros tenham valores padrão, isso não é considerado um "verdadeiro" construtor sem parâmetros no nível do bytecode da JVM.
\n\nQuando o Hibernate tenta chamar User.class.newInstance() via reflexão, ele não encontra um construtor vazio — e falha com erro.
Método 1: O mais simples — adicionar explicitamente um construtor secundário
\n\nA maneira mais direta é adicionar um segundo construtor vazio manualmente:
\n\ndata class User(\n val id: Long = 0,\n val name: String = ""\n) {\n // Construtor secundário sem parâmetros\n constructor() : this()\n}\n\n\nAqui, constructor() : this() significa: "chame o construtor primário com os valores padrão".
Vantagens:
\n- \n
- Muito simples e claro \n
- Não requer plugins ou configurações adicionais \n
Desvantagens:
\n- \n
- É necessário escrever este construtor para cada entidade \n
- Se você tem 10-15 entidades, fica tedioso \n
- Fácil de esquecer de adicionar \n
Método 2: Usar open class e tornar todas as propriedades var
\n\nMuitos exemplos na internet sugerem esta solução:
\n\nopen class User {\n var id: Long = 0\n var name: String = ""\n}\n\n\nAqui, tornamos a classe open (para que o Hibernate possa criar proxies) e as propriedades — var (mutáveis). E sem construtor primário — portanto, o Kotlin gerará um construtor vazio por padrão.
Vantagens:
\n- \n