Jdbi vs MyBatis vs jOOQ in a small Javalin REST API: memory and latency on a 512 MiB VM
I've been experimenting with lightweight Java applications on small VMs. After comparing Javalin with Spring Boot, I wanted to see how much the SQL persistence library affects resource usage.
I implemented the same small market-quote REST API using Jdbi Core, MyBatis Core, and jOOQ Open Source Edition.
Setup:
- JDK 25, Javalin 7, H2, HikariCP
- Alpine Linux VM, 1 vCPU, 512 MiB RAM, no swap
- JVM:
-Xms16m -Xmx80m -XX:+UseSerialGC -Xss512k - 1,500 requests over 5 minutes, 80% reads / 20% writes
- Two independent runs per implementation, rebooting the VM between runs
Results across the two runs:
| Library | Median workload RSS | HTTP p95 |
|---|---|---|
| Jdbi Core | 120.0 - 122.4 MiB | 7.38 - 8.48 ms |
| MyBatis Core | 116.6 - 118.0 MiB | 7.47 - 7.52 ms |
| jOOQ OSE | 152.1 - 154.3 MiB | 9.19 - 9.35 ms |
RSS is the median process resident memory sampled during the workload, including the application, JVM, and embedded H2.
All six runs completed successfully without errors, OOM events, or swapping.
A few observations:
- MyBatis had the lowest resident memory usage in both rounds.
- Jdbi was very competitive on both metrics, and required the least handwritten Java code.
- jOOQ used approximately 34–38 MiB more RSS than MyBatis, consistently across both runs. This is a whole-application difference, not necessarily memory consumed directly by the library. I haven't isolated its cause.
One implementation difference worth mentioning: Jdbi and MyBatis hold one pooled connection across the three latest-quote queries, while the datasource-backed jOOQ implementation checks out and returns a connection for each query. I haven't measured the impact of this difference.
I also configured MyBatis with localCacheScope=STATEMENT rather than its default SESSION scope.
From a developer experience perspective, the tradeoffs are interesting too. Jdbi keeps SQL and mapping close to the repository, MyBatis uses annotated mapper interfaces, and jOOQ offers type-safe SQL composition with generated schema classes.
For my own applications, I generally prefer MyBatis because I like its mapper-oriented programming model and explicit SQL. Its lower memory footprint in this experiment is another point in its favor, although I wouldn't choose a library based on memory usage alone.
Limitations:
This is a small, low-load case study, not a general performance ranking. The measurements include the complete application and embedded H2. Two runs show consistency in memory usage but aren't enough to establish statistically significant latency differences.
I also didn't include a raw JDBC baseline, which would be useful as a minimal reference point. PostgreSQL could produce different results.
Full write-up, charts, implementation sources, and raw measurements:
https://pvrlabs.xyz/java-performance/experiments/mybatis-jooq-jdbi-512mb.html
I'm interested in whether others have compared these libraries in similarly small applications, particularly with PostgreSQL, and how much the programming model influences your choice.