# kotlin-jdsl: JPA queries in Kotlin without a generated metamodel

> Kotlin JDSL, from the LINE organisation, is a library for building and executing JPA queries against your own entity classes, with no annotation processor and no generated metamodel in the build. The repository is a good study in how a query builder is split into parts, and its example directory is broad enough to show the library working inside real persistence stacks.

**line/kotlin-jdsl** — Kotlin library that makes it easy to build and execute queries without generated metamodel

- Repository: https://github.com/line/kotlin-jdsl
- Website: https://kotlin-jdsl.gitbook.io/docs/
- Stars: 826 · Forks: 103
- Language: Kotlin
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/line-kotlin-jdsl

## The claim is that you do not need generated code

Kotlin JDSL is described as a Kotlin library that makes query building and execution easy. You can create queries using your own classes and Kotlin's built-in functions, without an annotation processor, and execute them in your library.

That single sentence contains the whole design decision, and it is worth unpacking because it is the difference between this library and the usual approach to typed JPA queries.

The usual approach is a metamodel: an annotation processor reads your entities and generates a class per entity with static fields for the column names, so that a query can be written as a type-safe reference to a property rather than a string. The cost of that is a build step, generated sources, and a classpath that has to match.

Kotlin JDSL takes the opposite route. The query is built from your own entity classes and from ordinary Kotlin functions, which means there is no processor to configure and no generated code to keep in step with your entities. The cost of that is the mirror image: the library cannot check at compile time that the property you named is a real, persistent property of the entity, because there is nothing in the compiler's view that ties a string or a lambda to the schema. A wrong field name is a runtime failure rather than a build failure.

So the trade is build simplicity against compile-time safety, and the library is for teams who take the first side of that trade. The requirements are modest, Java 8 or later and Kotlin 1.7 or later, which is a friendly floor for an organisation with older services still in the estate.

## The repository is split into the parts a query builder needs

The directory listing of the repository is a decent map of the architecture, and it has more pieces than you would expect from a library that presents itself as one call.

There is a dsl/ directory, which is the surface a user writes against. A query-model/ directory, which is where the internal representation of a query lives. A render/ directory, which is the part that turns that model into something the persistence provider can run, and the naming is a strong hint that the library can render more than one dialect. There is a support/ directory, plus a src/ directory for the published artefacts, a docs/ directory, an example/ directory and a benchmark/ directory.

The separation between the model and the rendering is the design detail that matters most, and it is what makes the library usable across persistence providers. The query you write is a model, and the rendering step decides how to express it. If the query model were entangled with the query language, adding a new provider would mean rewriting the front end.

The benchmark directory is also a signal, since a query builder that nobody measures is a query builder whose performance is a guess. And the docs directory, together with the separate gitbook the README points at, means the reference documentation is maintained outside the repository, which is common for a library with a user base large enough to want a proper manual.

## The examples are the specification, and they span real stacks

The example directory is the most informative part of the repository, because a query builder is only as good as the stacks it has been proven against.

There is a set of examples built on Hibernate, in several variants: a plain one, one using the older javax namespace, and reactive variants in both flavours. There is a matching set built on EclipseLink, again with javax and reactive versions. There is a Spring Batch example, a second one for the newer major version of that framework, and Spring Data JPA examples in a plain form, a javax form and a boot4 form.

Two things fall out of that list. The first is that the library is not tied to one persistence provider, and the reactive and blocking APIs are both supported rather than one being an afterthought. The second is that the project is careful about namespace transitions, and the older javax examples are kept rather than deleted, so you can see how a query has to change when the namespace moves.

Each example has its own build file, its own readme and its own version catalog, with a pair of catalog files per example, one for the versions and one for the older versions the example is meant to work with. There is a shared example source directory as well, so the same code can be built against different provider combinations.

For an adopter, the practical instruction is short: find the example that matches your provider and your namespace and read it, because those directories are the library's documentation in executable form.

## Version management is treated as a first class concern

Dependency management is where a library like this spends more effort than its README suggests, and the repository shows it.

The root carries a version catalog for the library's own dependencies, and the example directory carries parallel catalogs for each example, one listing the current versions and one listing the versions used by the example that is meant to demonstrate an older setup. There is a Gradle properties file, a settings file and the main build file in the Kotlin DSL, and a Gradle wrapper with its Windows counterpart so that every build in the repository uses the same Gradle version.

There is also a configuration file for the dependency update bot, which means the project lets automation propose dependency bumps and a human review them. For a library whose whole value is that it works with your entities rather than replacing them, the set of versions it is verified against is genuinely part of the contract, and keeping that set explicit per example is the sensible way to publish it.

So the build story is: one Gradle build for the library, one per example, and shared catalogs that make the difference between an example running against current libraries and one running against the versions the library was originally written for.

The result is a repository where a contributor can add a new provider combination by copying an example directory and adjusting two version files, which is a reasonable thing for a project whose users are, by definition, people with different provider stacks.

## Repository conventions say who decides what

The smaller files in the repository root describe the project's process, and for a library maintained by a company they are more informative than they look.

The commit history is checked by a commit lint tool, configured through a JavaScript package in the repository that pins the commit lint command line and the conventional commit configuration to the same version. The repository has a hook directory, which is the local counterpart, and a code owners file, which is how review responsibility is assigned per path.

There is a code of conduct, a contributing guide and a file listing the licences of the open source components, which for a library that ends up inside other people's applications is worth having, and a coverage configuration with the coverage service badge in the readme.

The last file is a licence file and the project is under the Apache licence, which for a corporate open source project is the usual choice and has the usual implication: a patent grant and an explicit patent termination clause, which matters more to a company user than to an individual one.

The contact routes in the readme are the ones a company runs: a Discord server, a discussions forum, and an issue tracker, with the contributors list given its own section. There is no mailing list and no documentation address, and the documentation is a gitbook rather than a site hosted on the project's own domain, which is the pattern for a project whose users find it through a package index rather than through a website.

## Where this library fits, and what to check

A few closing observations for an adopter.

The project is maintained inside a large company, the LINE organisation, and the releases show a steady cadence: 3.8.1 and 3.8.2 in April 2026 and 3.9.0 in May, with the last push on 2026-10-19. That combination, a numbered release line with patch releases and a recent commit, says the library is maintained rather than merely published.

The first thing to check is the one the whole design implies, which is whether you are willing to lose compile-time checking of query fields. If your build already runs an annotation processor, for example because you use a metamodel generator for something else, then the main advantage of this library shrinks and its cost stands. If you do not, the advantage is real, and the absence of a processor in your build is a genuine simplification.

The second is the provider matrix, and the answer is in the examples rather than in the readme. The library presents itself as provider neutral, and the example directory is the evidence, with Hibernate and EclipseLink in both blocking and reactive flavours, and Spring Batch and Spring Data JPA on top.

The third is the version floor, which is unusually old in both directions, Java 8 and Kotlin 1.7, and the fourth is the licence, which for a company embedding this in a commercial product is the document to read first, along with the file that lists the licences of everything the library pulls in.

## Conclusion

Adopt kotlin-jdsl if you want a typed query builder for JPA in a Kotlin codebase and you would rather not put a generated metamodel and an annotation processor into your build, since the library works with your own classes and Kotlin's own functions. Do not adopt it if you depend on compile-time validation of query fields, because the point of the design is that the check happens at runtime instead. Before you plan an integration, look at the example directories for the persistence stack you actually use, because the examples differ in whether they use the older javax namespace or the reactive variants, and check the version catalog files, since the library pins a specific dependency set per example.

## FAQ

### What is Kotlin JDSL and what does it do?

It is a Kotlin library for building and executing JPA queries. You create queries using your own classes and Kotlin's built-in functions, without an annotation processor, and execute them against your own persistence setup, and the queries can be rendered for different providers.

### Does Kotlin JDSL need a generated metamodel?

No. The README says you can create queries using your own classes and Kotlin's built-in functions without an annotation processor, which is the library's main difference from metamodel based query builders. The trade is that query fields are not validated at compile time.

### Which persistence stacks does Kotlin JDSL support?

The example directory shows Hibernate and EclipseLink in blocking and reactive variants, including older javax namespace versions, plus Spring Batch, a newer major version of Spring Batch, and Spring Data JPA examples in plain, javax and Spring Boot 4 forms. Each example has its own build file and its own version catalog files.

### What are the requirements for using Kotlin JDSL?

Java 8 or later and Kotlin 1.7 or later for users of the library. The project is published to Maven Central under the com.linecorp.kotlin-jdsl group, and the library itself is Apache-2.0 licensed.

### How is the Kotlin JDSL repository maintained?

The root carries a code owners file, a contributing guide, a code of conduct, a local hook directory, a commit lint configuration with a matching JavaScript package pinned to the commit lint command line and the conventional commit configuration, a dependency update bot configuration, a coverage configuration, and a file listing the licences of the open source components it uses.

## Sources

- [License: Apache-2.0](https://github.com/line/kotlin-jdsl/blob/main/LICENSE)
- [line/kotlin-jdsl on GitHub](https://github.com/line/kotlin-jdsl)
- [Project website](https://kotlin-jdsl.gitbook.io/docs/)
- [README](https://github.com/line/kotlin-jdsl/blob/main/README.md)
- [Releases](https://github.com/line/kotlin-jdsl/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/line-kotlin-jdsl
