JHipster JDL: Model Once, Generate Everywhere, Pay the Regeneration Tax
JHipster's JDL gives fast, consistent Spring Boot and frontend CRUD, but editing generated files is a trap. Model shape in JDL, keep behavior in custom services outside the generated paths, and accept the regeneration tax.
17 Nov 2025, 13:58 UTC

Teams pick JHipster to avoid writing the same CRUD twice for Spring Boot and Angular/React/Vue. The speed is real until someone edits a generated controller, runs an entity update, and watches the change disappear. The useful takeaway is to treat generated layers as disposable scaffolding and keep all custom behavior in hand‑written services that the generator will not overwrite.
The regeneration problem hides in plain sight
JHipster scaffolds Liquibase changelogs, JPA entities, Spring Data repositories, Spring MVC REST controllers and client list/detail components from a single model. That consistency is valuable. It becomes a liability when business rules live inside generated files.
Regeneration is a first‑class operation. Importing or updating a JDL model rewrites files under src/main/java/.../domain, .../repository, .../web/rest and the frontend app folder. Manual edits to those paths are lost. The engineering decision is where to put code that must survive.
JDL as the single source of truth for shape, not behavior
JHipster's Domain Language is a text DSL for entities, fields, relationships and pagination. It drives both backend JPA and frontend TypeScript models. Relationship annotations like ManyToOne, OneToMany and ManyToMany control DTO generation and MapStruct mapper creation, which directly shapes the API surface and how the client fetches related data.
Paging is generated server‑side with Spring Data Pageable and Page<T>. The client components are pre‑wired to send page, size and sort parameters and render paged JSON. Changing to client‑side filtering later requires manual component and API changes.
Worked example: Department and Employee
Create a minimal department-employee.jh in the project root. Run the import from the project root with a JHipster CLI version that matches the project. The command requires write access to source and resource directories.
jhipster import-jdl department-employee.jh
A conservative JDL sketch for this pair:
entity Department {
name String required
code String required unique
}
entity Employee {
firstName String required
lastName String required
hireDate LocalDate
}
relationship ManyToOne {
Employee{department required} to Department{employees}
}
paginate Department with pagination
paginate Employee with pagination
Expected checks after import, without claiming a specific run result: a Liquibase changelog for both tables, a Spring Data repository for each entity, a REST resource exposing paged endpoints, and frontend list/detail components referencing the generated models. Verify by inspecting the generated files and confirming the JHipster version in package.json, pom.xml or build.gradle matches the JDL syntax you used. JDL syntax and DTO options have changed between major releases, so older .jh files may need manual adjustment after an upgrade.
Trade‑off: speed now, discipline later
The benefit is consistent boilerplate across the stack with one model change. The cost is the regeneration tax. If you add validation, security checks or domain logic into generated controllers or repositories, those edits will be overwritten on the next import-jdl or entity sub‑generator run.
Practical pattern: keep generated layers thin. Put custom logic in a separate service class that the generated service delegates to, or use JHipster's custom service hooks. Never edit files that the generator owns. This separation is the only reliable way to upgrade JHipster or regenerate entities without manual merge.
Limitations to plan for: generated pagination assumes server‑side paging. Microservice mode extends the same JDL workflow to gateway and resource‑server modules, but multiplies operational concerns like service discovery and distributed configuration. Exact CLI flags and file layouts vary across JHipster major versions, so confirm behavior against your version locally.
Actionable closing
Use JDL to own entity shape, relationships and paging. Treat generated CRUD as scaffolding. Put business logic in hand‑written services outside the generated paths, and verify after each import by checking Liquibase, repository and controller files for the expected structure. That keeps the speed of generation without losing work on the next regeneration.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.