Modeling Your Domain in One File: JHipster JDL for Entities, Relationships, and Pagination
JHipster's JDL lets you declare entities, relationships, and pagination in one versionable file and regenerate both tiers of a Spring Boot + Angular/React app. Here's a worked example, how to verify it, and the regeneration trade-off.
14 Sept 2026, 16:26 UTC

Every Spring Boot + Angular (or React) project starts the same way: someone sketches entities on a whiteboard, then three people spend a week hand-writing JPA classes, repositories, REST controllers, DTOs, and list screens — and the whiteboard sketch is already out of date by Friday. JHipster's answer to this is JDL, the JHipster Domain Language: a single text file that declares your entities, their relationships, and options like pagination, which the generator turns into working code on both tiers.
The thesis here is simple: JDL earns its keep when you treat it as the reviewable source of truth for your domain model, and it causes pain when you treat generated files as a place to write business logic. The rest of this post shows what that looks like in practice.
Why a text file beats a wiki page
A JDL file is plain text that lives in your repository. That means your data model goes through pull requests like everything else: a reviewer can see "added a required field to Post" as a three-line diff instead of spelunking through regenerated Java classes. Because JDL can also carry application-level configuration (base name, application type, database, build tool), one file can reproduce an entire project skeleton — handy if you run a fleet of microservices that should share conventions.
Tooling lowers the barrier further. JDL Studio, the browser-based editor, renders an entity diagram as you type, and IDE plugins offer syntax highlighting. New team members can read the model before they can read the syntax, which is the point.
A worked example: a blog with posts and comments
Here is a minimal but realistic JDL file. Exact keywords vary across JHipster major versions, so check the version pinned in your project before adapting it:
entity Post {
title String required
content TextBlob
}
entity Comment {
body String required
}
relationship OneToMany {
Post to Comment{post}
}
paginate Post with paginationThree things are happening. The entity blocks declare fields with bean-validation constraints (required becomes a @NotNull-style constraint). The relationship block makes Comment the owning side of a many-to-one back to Post — the {post} syntax names the injected field. The paginate line is the quiet workhorse: it tells the generator to build Spring Data Pageable-based REST endpoints for Post, plus front-end list components that understand page, size, and sort query parameters. You get server-side paging without writing offset math by hand.
To apply it, run the import from the project root (the directory containing your generated application), with the JHipster CLI installed and the project's generator version available:
jhipster jdl blog.jdlBefore running it on a real project, commit or stash your working tree. After the import, git status and git diff show you exactly which files the generator touched — this is the fastest way to learn what JHipster considers "its" files versus yours.
Verifying the result
Don't trust the generator blindly; check the output like you would any code change:
- Confirm the build compiles and the generated integration tests pass (for example,
./mvnw verifyor the Gradle equivalent, depending on your build tool). - Start the app and exercise the endpoint directly, e.g.
curl "http://localhost:8080/api/posts?page=0&size=5&sort=title,asc"with an authenticated session or token as your security config requires. With classicpagination, look for total-count information in the response headers (JHipster has historically used anX-Total-Countheader) and confirm the page size is honored. - Open the generated Post list view in the browser and click through pages and column sorting — the front end should issue the same parameters you tested with curl.
One caveat on relationships: bidirectional JPA associations are a classic source of infinite JSON recursion and lazy-loading exceptions in the REST layer. JHipster's generated code handles the common cases, but if you customize serializers or add your own endpoints, re-test both directions of Post ↔ Comment.
The trade-off: the generator is opinionated
JDL's weakness is regeneration. When the model changes and you re-run jhipster jdl, the generator rewrites its files — and if you edited those files by hand, you get conflicts or silent overwrites. Teams that succeed with JHipster tend to follow one rule: business logic lives in separate classes (your own services, listeners, or controllers), and generated code stays generated. JHipster documents extension patterns for this; the specific mechanism varies by version, so read the docs for yours.
Pagination behavior also isn't uniform. Defaults differ between imperative and reactive (WebFlux) stacks, and between monoliths and gateway/microservice topologies. If your fleet mixes those, verify each flavor rather than assuming the monolith's behavior carries over.
Where to start
Spin up a scratch project with the JHipster version your team actually uses, import the four-entity-or-fewer JDL file that mirrors your real domain, and diff the tree before and after a second regeneration. That fifteen-minute exercise tells you more about whether JDL fits your workflow than any feature list — and if it fits, your next data-model review happens in a pull request instead of a meeting.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.