Storm is the type-safe, SQL-first ORM for Kotlin 2.0+ and Java 21, built for agentic coding and performance. 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)
}The city graph loads in a single query, and User_ is generated at compile time so a typo is a compile error. What you write is what runs, which is also what makes Storm work so well with coding agents: the data class is the whole model, so the agent has the exact details it needs. Set your agent up with npx @storm-orm/cli init (Agentic Coding).
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("st.orm") version "1.14.1"
}On Kotlin 2.2 and newer the plugin applies KSP itself; on Kotlin 2.0 and 2.1, add id("com.google.devtools.ksp") with the version paired to your Kotlin line.
For Java, drop the Kotlin plugin:
plugins {
java
id("st.orm") version "1.14.1"
}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.14.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>Gradle (Kotlin DSL), Kotlin:
dependencies {
implementation(platform("st.orm:storm-bom:1.14.1"))
implementation("st.orm:storm-kotlin")
runtimeOnly("st.orm:storm-core")
ksp("st.orm:storm-metamodel-ksp")
kotlinCompilerPluginClasspath("st.orm:storm-compiler-plugin-2.0")
}Gradle (Kotlin DSL), Java:
dependencies {
implementation(platform("st.orm:storm-bom:1.14.1"))
implementation("st.orm:storm-java21")
runtimeOnly("st.orm:storm-core")
annotationProcessor("st.orm:storm-metamodel-processor")
}
tasks.withType<JavaCompile> {
options.compilerArgs.add("--enable-preview")
}
tasks.withType<Test> {
jvmArgs("--enable-preview")
}Both paths add the metamodel processor, which generates the User_ and City_ classes queries are written against, and the Java path sets the preview flags that Storm's string templates require. The Gradle plugin does all of that for you.
With the BOM imported, add Storm modules without specifying versions.
A coding agent is only as right as what it can read. In Storm the data class is the table, its keys and its relations, every database call is a visible line of code, and the domain model already gives one-line, type-safe queries across relations. There are no proxies, no lazy loading and no persistence context, so the code the agent reads is exactly what runs.
One command sets your agent up:
npx @storm-orm/cli init # in your project: rules, skills and schema access
npx @storm-orm/cli demo # in an empty directory: your agent builds a demo app on StormIt installs Storm's rules and skills for Claude Code, Cursor, GitHub Copilot, Windsurf and Codex. An existing CLAUDE.md or rules file keeps its content; Storm adds its own marked block. Optionally it sets up an MCP server that runs on your machine and is read-only: the agent sees your schema, never your credentials, and sees data only if you allow it. The agent then checks its own work with validateSchema() and SqlCapture.
See Agentic Coding 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, Ktor DI, 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 |
These are applications to read and run. To start a project of your own, begin from a starter template instead: one vertical slice of a working application, around three hundred lines, running on H2 the moment it is cloned.
| Template | Stack | Repository |
|---|---|---|
| Kotlin + Ktor | Kotlin, Ktor 3, H2 or PostgreSQL | storm-template-kotlin-ktor |
| Kotlin + Spring Boot | Kotlin, Spring Boot 4, H2 or PostgreSQL | storm-template-kotlin-spring-boot |
| Java + Spring Boot | Java 21, Spring Boot 4, H2 or PostgreSQL | storm-template-java-spring-boot |
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) |
| Entity Design | Choosing between inlined foreign keys and Refs (9 min) |
| Batch & Streaming | Bulk operations and Flow/Stream (5 min) |
| Upserts | Insert-or-update operations (6 min) |
| Write Sets | Dependency-ordered writes of mixed-type entity graphs (7 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 | Statement logging and per-call SQL summaries (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+ as its minimum supported Kotlin version; the Kotlin path runs on JDK 21 or later. The Java API uses String Templates, a preview feature, and preview class files are version-locked: the Java path compiles and runs on JDK 21 exactly until the platform reintroduces string templates as a stable feature. See String Templates for the full story.
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.
To get in touch with the engineers behind Storm, for example while evaluating it for your team or project, write to hello@orm.st. Already using Storm? Tell us what you built, and we will gladly list your company or project on orm.st.
If Storm is useful to you, a star helps other developers find it.
Storm is released under the Apache 2.0 License.