Using JHipster JDL to Model Relationships and Keep Custom Code Safe
Learn how to model entities and relationships with JHipster JDL and use incremental generation to preserve custom backend and frontend code.
01 Aug 2026, 18:31 UTC

Problem: Keeping generated code in sync with a evolving domain model
When a JHipster project grows, developers often need to add new entities, tweak fields, or adjust relationships. Running the entity sub‑generator repeatedly can overwrite custom logic that lives inside the generated Java classes, Angular components, or service files. The challenge is to evolve the domain model declaratively while preserving hand‑written code.
Thesis: JHipster’s Domain Language (JDL) lets you declare entities and relationships once, and the incremental import‑export workflow updates only the parts that changed, leaving custom code intact when you follow the protected‑region conventions.
Section 1: Defining a relationship in JDL
Start with a simple .jdl file that describes an Author entity that has many Book entities.
// model.jdl
entity Author {
name String required
}
entity Book {
title String required
publishedYear Integer
}
relationship OneToMany {
Author{book(s) required} to Book{author}
}
// Generation options
dto * with mapstruct
service * with serviceClass
pagination * with pagination
The dto * with mapstruct line tells JHipster to generate MapStruct‑based DTOs for every entity, service * with serviceClass creates a separate service layer, and pagination * with pagination adds Spring Data Pageable support to the REST endpoints.
Section 2: Importing the JDL incrementally
Assuming you already have a JHipster monorepo (generated with jhipster version 7.x or later), run:
# From the repository root
jhipster import-jdl model.jdl
This command:
- Parses the JDL and creates or updates the
.jhipster/JSON files that store the generation state. - Generates JPA entities (
Author.java,Book.java) with the proper@OneToMany/@ManyToOneannotations andmappedBy. - Adds a Liquibase changelog file under
src/main/resources/config/liquibase/changelog/that creates the tables and foreign key. - Creates Spring Data repositories, MapStruct mappers, service classes, and REST controllers.
- Generates Angular (or React/Vue) models, services, and component stubs.
Because the import is incremental, any existing .jhipster/Author.json or .jhipster/Book.json files are updated only where the JDL changed. Custom code that you placed inside JHipster‑marked sections (e.g., // jhipster-needle:add-field) survives the regeneration.
Section 3: Verifying the generated artifacts
After the import, you can confirm that the build works and the API behaves as expected:
# Verify annotation processing (MapStruct/Lombok)
./mvnw compile -DskipTests
# Start the backend and frontend (in separate terminals)
./mvnw
npm start
# Check the paginated endpoint
curl -s "http://localhost:8080/api/authors?page=0&size=5&sort=name,asc" | jq .
You should see a JSON payload with content, totalElements, totalPages, number, and size fields – the standard Spring Data Page structure. The Angular service generated for Author will expose a method like query(req?: any) that forwards the same query parameters to the backend.
Trade‑off / Limitation
While JDL excels at straightforward relationships, it does not model attributes on the join table of a ManyToMany association. If you need extra columns (e.g., a role field on an AuthorBook link), JHipster will create a bare join table and you must manually:
- Create an entity for the join table.
- Adjust the two owning entities to point to this new entity via
OneToMany/ManyToOnerelationships. - Update the Liquibase changelog to include the extra columns.
This manual step is the primary limitation when your domain requires rich association data.
Actionable closing
To adopt this workflow safely:
- Commit your current state (
git commit -am "Pre‑JDL import"). - Write or update your
.jdlfile with the desired entities, fields, relationships, and generation options. - Run
jhipster import-jdl your-file.jdl. - Inspect the diff (
git diff) to ensure only the expected files changed and that your custom code remains inside the protected regions. - Run the build and start the application to verify the API and UI.
- If you need to modify the JDL later, repeat the import; the incremental mode will preserve your hand‑written edits as long as they stay within the needle comments.
By treating the JDL as the single source of truth for your domain model, you reduce boilerplate, keep the generated code in sync, and retain full control over the parts that require custom logic.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.