Hibernate Reactive on Kotlin & Ktor
It’s like when no bus comes for an hour, and then suddenly two arrive at once. Well, I’m between jobs right now, so I have a bit of free time — hence the flurry of blog posts.
In my day-to-day work, I usually program in TypeScript, but it’s no secret that if given the choice, I’d happily build things in Kotlin. I’ve been doing that on weekends for a while now. As I’ve mentioned before, I like Kotlin because it’s almost like TypeScript — but with the huge advantages of immutability and the multi-core JVM (plus the rich JVM ecosystem).
I usually try to build small projects in Kotlin during my free time. The frameworks I typically explore are:
I’ve used Spring Boot a lot, so when experimenting, I prefer to try something different. Micronaut and Quarkus are solid alternatives, but they’re quite close to Spring Boot — just with smaller communities (maybe 10% of the community size). I like Javalin the most (it’s very close to Express or Sinatra), but it’s still stuck on Jetty 11. While Javalin 7 should be out soon, I can’t hold my breath any longer.
That leaves Ktor or Http4K. Http4K is nice, but its community is even smaller. So Ktor feels like the best option — with decent docs, an active community, and regularly updated libraries.
Database Options
Now, how should we interact with the database?
Ktor recommends Exposed, but I find it quite cumbersome. For lightweight apps, I prefer jOOQ, and if a full ORM is needed, Hibernate is still the safest choice out there.
The JVM Thread Problem
The big challenge with the JVM is thread blocking.
In the Node.js world, if a request is waiting for the database to respond, the CPU isn’t wasted — it can serve other requests in the meantime.
In Java, however, each request typically runs on a thread, and if that thread is waiting on the database, it just … waits. It’s blocked (and wasting resources)! 😞
There are two main ways to address this in the Java world:
- Virtual Threads
- Reactive Java
Virtual Threads are promising, but support outside of the latest Spring releases is still limited — and there can be surprises.
As for Reactive Java … let’s just say it’s not the most concise approach (as if Java wasn’t verbose enough already).
That’s why, even in 2025, I wouldn’t recommend pure Java if your goal is to save on cloud costs.
Kotlin to the Rescue
With Ktor, we get full coroutine support, which makes it a great choice outside the Spring ecosystem.
However, since jOOQ uses blocking JDBC queries, we need a non-blocking alternative for database access.
Hibernate Reactive provides exactly that. It’s backed by the Hibernate team and is fully non-blocking.
So now we want to combine:
- Ktor
- Hibernate Reactive
- Kotlin coroutines
Out of the box, Hibernate Reactive doesn’t provide Kotlin helpers — that’s a small red flag 🚩 — but it’s built on SmallRye Mutiny, which does have Kotlin support. So there’s hope!

A Note of Caution
- We’re in somewhat experimental territory here. I really wonder if someone actually uses this combination in production.
- If you want a safer choice, go with Spring WebFlux + Kotlin + R2DBC.
- But if no one ever experiments, how do we move forward? 😁
Setting It Up
Good news: Kotlin + Coroutines + Hibernate Reactive can work together!
In your Ktor project, add these dependencies:
implementation("org.hibernate.reactive:hibernate-reactive-core:4.1.3.Final")implementation("io.vertx:vertx-pg-client:5.0.5")implementation("org.hibernate.validator:hibernate-validator:9.0.1.Final")implementation("org.glassfish:jakarta.el:4.0.2")implementation("io.smallrye.reactive:mutiny-kotlin:2.0.0")The thing to note here is there is no JDBC driver, we instead use the “vertx-pg-client” which is a non-blocking driver.
To get Hibernate working, we need a “persistence.xml” file -
<persistence xmlns="https://jakarta.ee/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd" version="3.0"> <persistence-unit name="postgresql-example"> <provider>org.hibernate.reactive.provider.ReactivePersistenceProvider</provider>
<class>dev.rockyj.todo.domain.entities.User</class>
<properties> <!-- PostgreSQL --> <property name="jakarta.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/demo_app_dev"/>
<!-- Credentials --> <property name="jakarta.persistence.jdbc.user" value="demo_app_user"/> <property name="jakarta.persistence.jdbc.password" value="secret123"/>
<!-- The Vert.x SQL Client connection pool size --> <property name="hibernate.connection.pool_size" value="10"/>
<property name="jakarta.persistence.schema-generation.database.action" value="validate"/>
<!-- SQL statement logging --> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.highlight_sql" value="true"/> </properties> </persistence-unit></persistence>Our “Entity” pretty much looks like a standard JPA entity -
package dev.rockyj.todo.domain.entities
import dev.rockyj.todo.domain.dtos.UserDTO// data class UserDTO(val id: UUID, val email: String?)import jakarta.persistence.*import org.hibernate.Hibernateimport java.io.Serializableimport java.util.*
@Entity@Table(name = "users")class User : Serializable {
@Id @GeneratedValue(strategy = GenerationType.UUID) @Column(name = "id", nullable = false, updatable = false) lateinit var id: UUID
@Column(name = "email", columnDefinition = "TEXT", nullable = false) var email: String? = null
@Column(name = "password_hash", columnDefinition = "TEXT", nullable = false) var passwordHash: String? = null
fun toDTO(): UserDTO { return UserDTO(this.id, this.email) }
override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || Hibernate.getClass(this) != Hibernate.getClass(other)) return false other as User
return id == other.id }
override fun hashCode(): Int = id.hashCode()}And now for the final piece of the puzzle, how to run queries using Kotlin coroutines and Hibernate which only supports “Mutiny”.
First we need the right Hibernate “session factory” -
package dev.rockyj.todo.config
import jakarta.persistence.Persistenceimport org.hibernate.reactive.mutiny.Mutiny
object HibernateConfig { val sessionFactory: Mutiny.SessionFactory by lazy { val emf = Persistence.createEntityManagerFactory("postgresql-example") emf.unwrap(Mutiny.SessionFactory::class.java) }}And then with the Mutiny + Kotlin integration we can get this working in a Ktor router -
fun Application.configureRouting() { routing { route("/api/v1") { // Health check endpoint get("/health") { val threadInfo = mapOf( "thread_name" to Thread.currentThread().name, "is_virtual" to Thread.currentThread().isVirtual )
val message = Uni.createFrom().item("Mutiny ❤ Kotlin").awaitSuspending()
// A sample DB Query val data = sessionFactory .withSession { session -> session.find(User::class.java, UUID.randomUUID()) } .awaitSuspending() ?.toDTO()
call.respond( HttpStatusCode.OK, mapOf( "status" to "healthy", "timestamp" to System.currentTimeMillis(), "thread_info" to threadInfo, "message" to message, "user" to data ) ) } } }}Please note that in this case I put the query in the router itself, but in a real-world application you will probably put this in a separate repository class. The main thing here is the awaitSuspending function which converts the Uni to a “suspend” function.
That is it! We now have a fully non-blocking Ktor + Hibernate Reactive setup, which should scale & perform really well. Happy programming!