Managing Custom Logic and Entity Regeneration in JHipster
Avoid the 'regeneration trap' in JHipster by separating business logic from generated entities. Learn the delegation pattern to preserve custom code during scaffold updates.
04 Oct 2026, 13:15 UTC

The primary challenge when using JHipster is the "regeneration trap": modifying a generated class (like a Service or Resource) only to have those changes overwritten the next time you run the generator to add a field or change a relationship. To maintain custom business logic, you must separate your code from the files JHipster manages.
The Mechanism of Code Regeneration
JHipster uses a template-based system powered by Yeoman. When you run jhipster entity or jhipster --jdl, the generator reads the entity definitions stored in the .jhipster/ directory (the project root configuration folder). It then regenerates the corresponding Java classes, Liquibase changelogs, and frontend components to match the current definition.
Because JHipster does not provide a native "ignore list" for specific files within a generated entity, any direct modification to a generated class is volatile. The established engineering pattern is to use Delegation or Composition: create a separate, non-generated class to handle complex logic and call that class from the generated service.
Worked Example: Implementing a Custom Validator
Suppose you have a Product entity and need to implement a complex SKU validation that depends on an external API or a specific business rule that cannot be expressed in a JDL (JHipster Domain Language) constraint.
- Define the entity in JDL:
entity Product { String name, BigDecimal price, String sku } - Generate the scaffold:
Run
jhipster --jdl app.jdlfrom the project root. This creates theProductService.javaandProductResource.javafiles. - Create a dedicated Logic class:
Create a new class in a package that JHipster does not manage. For example,
src/main/java/com/mycompany/myapp/service/ProductBusinessLogic.java.package com.mycompany.myapp.service; import org.springframework.stereotype.Service; import com.mycompany.myapp.domain.Product; import org.springframework.web.api.HttpEntity; import org.springframework.web.server.ResponseStatusException; import org.springframework.http.HttpStatus; @Service public class ProductBusinessLogic { public void validateSku(Product product) { if (product.getSku() != null && product.getSku().startsWith("FORBIDDEN")) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid SKU format"); } } } - Inject and call the logic in the generated service:
Modify
ProductService.javato injectProductBusinessLogic. While this file may be overwritten during regeneration, the logic itself remains safe in the separate class. You only need to re-add the method call to the generated service if you regenerate the entity.// In ProductService.java private final ProductBusinessLogic businessLogic; public Product save(Product product) { businessLogic.validateSku(product); return productRepository.save(product); }
Verification and Diagnostics
To ensure your entity changes are correctly applied and your custom logic is integrated:
- Database Schema: Check
src/main/resources/config/liquibase/changelog/. Verify that a new XML changelog file exists for theProductentity and that the columns match your JDL. - Build Integrity: Run
./gradlew build(for Gradle) or./mvnw clean install(for Maven) to ensure that the manual injection of the business logic class hasn't introduced compilation errors. - Functional Check: Use a tool like Postman or the Swagger UI (typically at
/swagger-ui.html) to attempt to save a product with a "FORBIDDEN" SKU and verify the400 Bad Requestresponse.
Limits and Common Mistakes
- Modifying DTOs: Placing logic inside Data Transfer Objects (DTOs) is a common mistake. DTOs are frequently regenerated to match entity field changes; any logic placed there will be lost.
- Liquibase Circularity: When defining many-to-many relationships in JDL, failing to specify the relationship ownership can lead to circular dependencies in the Liquibase changelogs, causing database migration failures during startup.
- Blueprint Mismatches: If using a custom JHipster blueprint, ensure the blueprint version matches the JHipster CLI version. A mismatch can result in templates rendering with outdated Spring Boot annotations, leading to runtime bean injection failures.
- Over-reliance on Manual Edits: If you find yourself manually editing the same generated files repeatedly, consider creating a Custom Blueprint. Blueprints allow you to override the actual Yeoman templates used by JHipster, making your structural changes permanent across regenerations.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.