Storm is the type-safe, SQL-first ORM for Kotlin 2.0+ and Java 21+. Immutable data-class entities, one-line queries checked at compile time, and none of the machinery you fight in traditional ORMs: no proxies, no N+1, no persistence context.
// An entity is a data class. This is the whole mapping.
data class User(
@PK val id: Int = 0,
val email: String,
@FK val city: City,
) : Entity<Int>
// Query nested properties in one line, checked at compile time.
val users = orm.findAll(User_.city.name eq "Sunnyvale")
// Repositories inherit CRUD. Add only the queries that are yours.
interface UserRepository : EntityRepository<User, Int> {
fun findByCity(name: String) = findAll(User_.city.name eq name)
}
// Drop to SQL whenever you want it. Interpolations become bind parameters.
val users = orm.query { """
SELECT ${User::class}
FROM ${User::class}
WHERE ${User_.city.name} = $cityName""" }.resultList<User>()The city graph loads in a single query, User_ is generated at compile time so a typo is a compile error, and every interpolation is a bind parameter. What you write is what runs.
Storm draws on decades of ORM experience but starts from a different premise: capture exactly what you want to do in the fewest lines, stay close to SQL, and keep the runtime transparent. Records mirror your schema, queries mirror SQL, and nothing happens that you did not write.
| Traditional ORM pain | Storm |
|---|---|
| N+1 queries from lazy loading | Entity graphs load in a single query |
| Hidden magic: proxies, implicit flush, cascades | Stateless records, explicit and predictable |
| Entity state confusion: managed, detached, transient | Immutable records, no state to manage |
| Entities tied to a session or context | Stateless records, cached and shared across layers |
| Dirty checking via bytecode manipulation | Dirty checking is free, thanks to immutability |
| Complex mapping configuration | Convention over configuration |
| Runtime query errors | Compile-time, type-safe DSL |
| SQL hidden behind abstraction layers | SQL-first: you stay close to the database |
Storm is built for developers who want the object model and the database model to work in harmony. Custom mappings are there when you need them, but the elegance comes from alignment, not abstraction.
Both Kotlin and Java use SQL templates for query composition. Kotlin adds a type-safe DSL with infix operators, coroutines, and Flow streaming.
// Block DSL: where, orderBy, joins, pagination.
val users = userRepository.select {
where(User_.city.name eq "Sunnyvale")
orderBy(User_.name)
}.resultList
// Stream results as a Flow.
val stream: Flow<User> = orm.entity(User::class).select().resultFlow
// Programmatic transactions, coroutine-friendly.
transaction {
val city = orm insert City(name = "Sunnyvale", population = 161_884)
orm insert User(email = "bob@example.com", name = "Bob", city = city)
}Prefer Java? The same model works with records and Java string templates. See the Java + Spring Boot example and the String Templates guide.
String Templates: Kotlin uses a compiler plugin that wraps interpolations at compile time; Java uses String Templates, a preview feature. See String Templates for setup in both languages.
New to Storm? The fastest path is the 5-minute Quickstart: install, define an entity, run a type-safe query.
The Storm Gradle plugin is the whole setup in one block: it imports the BOM, adds the core dependencies, wires the metamodel processor, selects the compiler-plugin variant for your Kotlin version, and sets the Java preview flags. Requires Gradle 8.5+.
plugins {
kotlin("jvm") version "2.4.0"
id("com.google.devtools.ksp") version "2.3.10"
id("st.orm") version "1.13.0"
}For Java, drop the Kotlin and KSP plugins:
plugins {
java
id("st.orm") version "1.13.0"
}Add a database dialect and any integrations as ordinary dependencies; versions come from the BOM the plugin imports, so you never specify them per module:
dependencies {
runtimeOnly("st.orm:storm-postgresql")
implementation("st.orm:storm-kotlin-spring-boot-starter")
}Storm is modular; you add only what your stack uses. Versions come from the BOM, so you never specify them per module.
| Your stack | Add |
|---|---|
| Kotlin (any) | storm-kotlin + storm-core (runtime) + storm-metamodel-ksp (ksp) + storm-compiler-plugin-2.x |
| Kotlin + Spring Boot | storm-kotlin-spring-boot-starter |
| Kotlin + Ktor | storm-kotlin + storm-ktor |
| Java 21 | storm-java21 + storm-core (runtime) + storm-metamodel-processor |
| Java + Spring Boot | storm-spring-boot-starter |
| + your database | one dialect (runtime): storm-postgresql, storm-mysql, storm-mariadb, storm-oracle, storm-mssqlserver, storm-sqlite, storm-h2 |
| + JSON columns | storm-jackson2, storm-jackson3, or storm-kotlinx-serialization |
| + metrics & tracing | storm-micrometer (included in the Spring Boot starters) |
| + Spring Boot test slice | storm-spring-boot-test-autoconfigure (test scope, @DataStormTest) |
See Installation for the full module overview.
Without the Gradle plugin, import the BOM once and add the modules yourself. On Gradle the compiler-plugin variant matches your Kotlin major.minor version (storm-compiler-plugin-2.0 for Kotlin 2.0.x, -2.1 for 2.1.x, and so on).
Maven:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>st.orm</groupId>
<artifactId>storm-bom</artifactId>
<version>1.13.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>Gradle (Kotlin DSL):
dependencies {
implementation(platform("st.orm:storm-bom:1.13.0"))
implementation("st.orm:storm-kotlin")
runtimeOnly("st.orm:storm-core")
kotlinCompilerPluginClasspath("st.orm:storm-compiler-plugin-2.0")
}With the BOM imported, add Storm modules without specifying versions.
Storm's stateless, immutable model is a natural fit for AI coding tools: what you see in the source is exactly what runs, with no proxies, lazy loading, or hidden persistence-context rules to trip up generated code. An optional workflow gives AI tools full schema awareness through a local MCP server, guides them with Storm-specific skills, and closes the loop by verifying generated entities and queries with real tests rather than trusting model reasoning.
npm install -g @storm-orm/cli
storm initSee AI-Assisted Development for the full workflow.
Three complete, runnable Storm Movies applications, each importing the public IMDB dataset and rendered inline at orm.st/examples:
| Example | Stack | Repository |
|---|---|---|
| Kotlin + Ktor | Kotlin, Ktor 3, Koin, PostgreSQL | storm-example-kotlin-ktor |
| Kotlin + Spring Boot | Kotlin, Spring Boot 4, PostgreSQL | storm-example-kotlin-spring-boot-4 |
| Java + Spring Boot | Java 21, Spring Boot 4, PostgreSQL | storm-example-java-spring-boot-4 |
Full documentation is available at orm.st.
Everything you need to build applications with Storm. Start with Getting Started and work through the topics as needed.
| Topic | Description |
|---|---|
| Getting Started | Installation and first steps (7 min) |
| Entities | Defining entities, annotations, naming (12 min) |
| Projections | Read-only database views (8 min) |
| Relationships | One-to-one, many-to-one, many-to-many (13 min) |
| Repositories | Repository pattern and custom methods (5 min) |
| Queries | Select, filter, aggregate, order (8 min) |
| Metamodel | Compile-time type safety (10 min) |
| Refs | Lazy loading and optimized references (7 min) |
| Batch & Streaming | Bulk operations and Flow/Stream (5 min) |
| Upserts | Insert-or-update operations (6 min) |
| Polymorphism | Sealed type inheritance strategies (20 min) |
| Entity Lifecycle | Callbacks for auditing, validation, and logging (8 min) |
| JSON Support | JSON columns and aggregation (6 min) |
| Transactions | Transaction management and propagation (22 min) |
| Spring Integration | Spring Boot Starter and auto-configuration (8 min) |
| Database Dialects | Database-specific support (5 min) |
| Testing | JUnit 5 integration and statement capture (5 min) |
| Validation | Record and schema validation (5 min) |
Deep dives into Storm's internals. You don't need these to be productive, but they help you understand what happens under the hood and optimize performance.
| Topic | Description |
|---|---|
| String Templates | Kotlin compiler plugin and Java string templates (5 min) |
| SQL Templates | Template parameters and query generation (10 min) |
| Hydration | Result mapping to records (16 min) |
| Dirty Checking | Update modes and change detection (19 min) |
| Entity Cache | Transaction-scoped caching and identity (10 min) |
| Configuration | System properties reference (7 min) |
| SQL Logging | Declarative query logging with @SqlLog (6 min) |
| Metrics | JMX runtime metrics for monitoring (5 min) |
Guides for evaluating Storm and transitioning from other frameworks.
| Topic | Description |
|---|---|
| Comparison | Storm vs other frameworks |
| FAQ | Frequently asked questions |
| Migration from JPA | Transitioning from JPA/Hibernate |
Storm works with any JDBC-compatible database. Dialect packages provide optimized support for:
Storm targets Kotlin 2.0+ and Java 21+ as minimum supported versions. These baselines will be maintained for the foreseeable future.
We welcome contributions! See CONTRIBUTING.md for guidelines.
Have a question, an idea, or something you built with Storm? Join the conversation in GitHub Discussions:
- Ask a question if you are getting started or stuck.
- Share an idea for a future release.
- Show what you built.
If Storm is useful to you, a star helps other developers find it.
Storm is released under the Apache 2.0 License.