Defining Paginated Entities with JHipster JDL: Import, Verify, and Recover
Define a paginated entity in JHipster's JDL, import it to generate the full Spring Boot and front-end stack, and verify pagination via the X-Total-Count header — with rollback steps for bad imports.
09 Jul 2026, 17:01 UTC

The problem JDL solves
Hand-writing a JPA entity, a Spring Data repository, a REST controller, a Liquibase migration, and a front-end CRUD screen for every table is repetitive and error-prone. JHipster's JDL (JHipster Domain Language) lets you declare entities once in a single file and generate all of that consistently — including a detail many teams forget until production: server-side pagination on list endpoints. This guide walks through defining a Project entity with pagination, importing it, and verifying the result end to end.
The practical takeaway: if you omit the paginate directive, the generated list endpoint loads every row in the table. Declaring pagination in JDL is one line; retrofitting it later means regenerating code over your edits.
Prerequisites
- A supported LTS Node.js version and a JDK matching your JHipster release. These requirements change between major versions, so run
jhipster --versionand check that release's documentation before starting. - The generator installed globally:
npm install -g generator-jhipster. - A scaffolded application (from running
jhipsterin an empty directory) or an existing JHipster project. - A clean Git working tree. JDL import touches dozens of files; being able to run
git diffafterward is your review and rollback mechanism.
Write the JDL with pagination declared
Create a file named app.jdl in the project root. A minimal example:
entity Project {
name String required minlength(3),
description String,
startDate LocalDate
}
paginate Project with pagination
service Project with serviceClassThree details matter here:
paginate Project with paginationgenerates a page-based endpoint using Spring Data conventions. The alternative,infinite-scroll, uses the same paginated backend but a different client UI. Omitting the directive entirely produces a "load all rows" list endpoint — fine for a 50-row lookup table, a performance problem for anything that grows.service Project with serviceClassadds a service layer between controller and repository. This is optional, but it gives you a non-generated place for business logic, which matters because re-importing JDL can overwrite hand edits inside generated classes.- Field validations (
required,minlength) propagate to the database schema, the REST validation, and the client form.
Import and start the application
From the project root, with a clean Git tree:
jhipster jdl app.jdlThis generates the JPA entity, repository, REST controller, a Liquibase changelog, and the front-end CRUD module (Angular or React, per your app's configuration). Then start the backend and client:
./mvnw # backend, Linux/macOS; use mvnw.cmd on Windows
npm start # client dev server, in a second terminalIf your project uses Gradle, substitute ./gradlew. No elevated permissions are needed; the backend binds to port 8080 by default and the dev server proxies API calls to it.
Verify the result
Check three layers:
- Database migration. Confirm a new changelog file exists under
src/main/resources/config/liquibase/changelog/and is referenced inconfig/liquibase/master.xml. At startup, Liquibase applies it — watch the logs for the changeset executing rather than assuming it ran. - Paginated endpoint. With the backend running, request the first page:
curl -i "http://localhost:8080/api/projects?page=0&size=5&sort=id,asc" \
-H "Authorization: Bearer <your-jwt>"Expect HTTP 200, at most 5 items in the body, and an X-Total-Count response header. The generated client reads that header to render page controls, so its absence means pagination is not actually active. If your app uses session authentication instead of JWT, authenticate through the browser and check the request in dev tools instead.
- Front end. Open the Project list page in the running app (typically under the Entities menu) and click through pages. In browser dev tools, confirm each page change triggers a new request with different
pageparameters — this proves the client is not just slicing a fully loaded array.
Also run git status and skim git diff after the import to confirm only expected files were created or modified.
Changing the entity later
Edit the JDL (add a field, a relationship, a validation) and re-run jhipster jdl app.jdl, or use the entity sub-generator (jhipster entity Project). The key check: a new incremental Liquibase changelog should be generated for the schema change. Never edit an already-applied changelog file — Liquibase checksums applied changesets and will fail at startup if one is altered. Keep custom logic in separate classes or in the service layer so re-imports do not clobber it.
Recovery options
Because you committed before importing, a bad import is reversible:
git checkout . # discard modifications to tracked files
git clean -fd # remove newly generated untracked filesIf the application was already started and Liquibase applied the new table to a development database, also drop that table (or reset the dev database) before re-importing, otherwise the next startup will try to create it again. In production, never roll back by deleting changelogs — write a new migration instead.
Limitations worth knowing
- The
paginationoption issues a count query forX-Total-Count. On very large or heavily filtered tables this count can be expensive; measure with realistic data before assuming it scales, and considerinfinite-scrollor custom endpoints if it does not. - Relationship defaults in JDL (fetch behavior, many-to-many ownership) affect generated queries and DTOs in ways that are easy to misconfigure. Review the generated repository methods if a relationship endpoint behaves unexpectedly.
- Generated defaults and supported Node/Java versions shift between JHipster major releases. Treat version-specific details here as assumptions to confirm against your generator's release notes.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.