Storm vs Other Frameworks
There is no universally "best" database framework. Each has strengths suited to different situations, team preferences, and project requirements. Teams approach data access differently, including using frameworks at various abstraction levels or even plain SQL. This page provides a comparison to help you evaluate whether Storm fits your needs, particularly if you value explicit and predictable behavior and fast development. We encourage you to explore the linked documentation for each framework and form your own conclusions.
Feature Comparison
The following tables provide a side-by-side comparison of concrete features across all frameworks discussed on this page. "Yes" and "No" indicate built-in support; "Manual" means the feature is achievable but requires explicit effort from the developer.
Entity & Data Modeling
| Feature | Storm | JPA | Spring Data | MyBatis | jOOQ | JDBI | Exposed | Ktorm |
|---|---|---|---|---|---|---|---|---|
| Lines per entity | ~5 | ~301 | ~301 | ~20+ | Generated | ~15 | ~12 | ~15 |
| Immutable entities | Yes | No | No | Yes | Yes | Yes | DSL only | No |
| Polymorphism | Yes2 | Yes | Via JPA | No | No | No | No | No |
| Automatic relationships | Yes | Yes3 | Via JPA | No | No | No | DAO only | No |
| Cascade persist | No | Yes | Yes | No | No | No | No | No |
| Lifecycle callbacks | Yes | Yes | Via JPA | No | Yes | No | DAO only | No |
1 JPA/Spring Data lines without Lombok; ~10 lines with Lombok.
2 Storm supports Single-Table, Joined Table, and Polymorphic FK strategies using sealed types. JPA additionally supports Table-per-Class and multi-level inheritance hierarchies.
3 JPA relationships are runtime-managed via proxies.
Querying & Data Access
| Feature | Storm | JPA | Spring Data | MyBatis | jOOQ | JDBI | Exposed | Ktorm |
|---|---|---|---|---|---|---|---|---|
| Type-safe queries | Yes | Criteria | No | No | Yes | No | Yes | Yes |
| SQL Templates | Yes | No | No | XML/Ann | Yes | Yes | No | No |
| N+1 prevention | Yes | No | No | No | Manual | Manual | No | No |
| Lazy loading | Refs | Yes | Yes | No | No | No | Yes | Yes |
| Scrolling | Yes | No | Yes | No | Yes | No | No | No |
| JSON columns | Yes | Yes4 | Via JPA | Manual | Yes | Module | Yes | Module |
| JSON aggregation | Yes | No | No | No | Yes | No | No | No |
4 JPA requires Hibernate 6.2+ for built-in JSON support; older versions need a third-party library or custom AttributeConverter.
Runtime & Ecosystem
| Feature | Storm | JPA | Spring Data | MyBatis | jOOQ | JDBI | Exposed | Ktorm |
|---|---|---|---|---|---|---|---|---|
| Transactions | Both | Both | Declarative | Both | Programmatic | Both | Both6 | Required |
| Schema validation | Yes | Yes | Via JPA | No | N/A5 | No | Yes | No |
| Java support | Yes | Yes | Yes | Yes | Yes | Yes | No | No |
| Kotlin support | First-class | Good | Good | Good | Good | Good | Native | Native |
| Coroutines | Yes | No | No | No | No | No | Yes | Limited |
| Spring integration | Yes | Yes | Native | Yes | Yes | Yes | Yes | Yes |
| Runtime mechanism | Codegen7 | Bytecode | Bytecode | Reflection | Codegen | Reflection | Reflection | Reflection |
| Community | New | Huge | Huge | Large | Medium | Medium | Medium | Small |
5 jOOQ generates code from the database schema, so schema validation is inherent in its code generation step.
6 Exposed requires transaction {} blocks natively, but supports declarative @Transactional via its Spring integration module.
7 Storm uses codegen with reflection fallback.
Storm vs JPA/Hibernate
JPA (typically implemented by Hibernate) is the most widely used persistence framework in the Java ecosystem. It provides a full object-relational mapping layer with managed entities and second-level caching. Storm takes a fundamentally different approach: entities are plain values with no managed state, and database interactions are explicit rather than implicit. This makes Storm simpler to reason about at the cost of JPA's more automated (but less predictable) features.
| Aspect | Storm | JPA/Hibernate |
|---|---|---|
| Entities | Immutable records/data classes | Mutable classes with getters/setters |
| Polymorphism | Sealed types (Single-Table, Joined, Polymorphic FK); STRING, INTEGER, CHAR discriminators | Class hierarchy (Single-Table, Joined, Table-per-Class); STRING, INTEGER, CHAR discriminators |
| State | Stateless; no persistence context | Managed entities |
| Loading | Loading in single query | Lazy loading common |
| N+1 Problem | Prevented by design; requires explicit opt-in | Common pitfall |
| Queries | Type-safe DSL, SQL Templates | JPQL, Criteria API |
| Caching | Transaction-scoped observation | First/second level cache |
| Transactions | Programmatic + @Transactional (Spring) | @Transactional, JTA, container-managed |
| Schema Validation | Programmatic + Spring Boot | ddl-auto=validate |
| Learning Curve | Gentle; SQL-like | Steep; many concepts |
| Magic | What you see is what you get | Proxies, bytecode enhancement |
Polymorphism differences. Storm and JPA overlap on Single-Table and Joined Table, but diverge beyond that. Storm adds Polymorphic FK, a two-column foreign key (type + id) that references independent tables with no shared base. This has no JPA equivalent (Hibernate offers the non-standard @Any annotation for a similar purpose). JPA adds Table-per-Class, which duplicates all fields into per-subtype tables and queries the base type via UNION ALL, and multi-level inheritance (e.g., Animal → Pet → Cat). Storm intentionally limits hierarchies to a single sealed level, which covers the vast majority of real-world use cases while keeping SQL generation predictable.
When to Choose Storm
- You want predictable, explicit database behavior
- You want concise entity definitions with minimal boilerplate
- N+1 queries have been a recurring problem
- You prefer immutable data structures
- You value simplicity over complexity
- You want a lightweight, minimal dependency footprint
- You're using Kotlin and want idiomatic APIs
When to Choose JPA/Hibernate
- You rely on second-level caching
- You have complex multi-level inheritance hierarchies (Storm supports single-level sealed type polymorphism)
- You have an existing JPA codebase to maintain
- You need JPA compliance for vendor reasons
- You want access to a large community and extensive resources
Storm vs Spring Data JPA
Spring Data JPA wraps JPA with a repository abstraction that derives query implementations from method names. It reduces boilerplate but inherits all of JPA's runtime complexity (proxies, managed state, lazy loading). Storm's Spring integration provides similar repository convenience with explicit query bodies instead of naming conventions.
| Aspect | Storm | Spring Data JPA |
|---|---|---|
| Foundation | Custom ORM | JPA/Hibernate |
| Polymorphism | Sealed types (Single-Table, Joined, Polymorphic FK) | Via JPA |
| Repositories | Interface with default methods | Interface with method naming, @Query |
| Query Methods | Explicit DSL in method body | Derived from method names, @Query |
| Entities | Records/data classes | JPA entities |
| State | Stateless | Managed |
| Transactions | Programmatic + @Transactional (Spring) | @Transactional (Spring-managed) |
When to Choose Storm
- You want stateless, immutable entities with minimal boilerplate
- You prefer explicit query logic over naming conventions
- You want to avoid JPA's complexity
- You want a lightweight, minimal dependency footprint
When to Choose Spring Data JPA
- You need JPA features (lazy loading, caching)
- You like query derivation from method names
- You're already invested in the JPA ecosystem
Storm vs MyBatis
MyBatis is a SQL mapper that gives you full control over every query. You write SQL in XML files or annotations and map results to POJOs manually. Storm sits at a higher abstraction level, inferring SQL from entity definitions while still allowing raw SQL when needed. The trade-off is flexibility vs. automation: MyBatis never generates SQL for you, while Storm handles the common cases and lets you drop to raw SQL for complex queries.
| Aspect | Storm | MyBatis |
|---|---|---|
| Approach | Stateless ORM | SQL mapper |
| Polymorphism | Sealed types (Single-Table, Joined, Polymorphic FK) | Manual (via SQL) |
| SQL Definition | Inferred from entities, SQL Templates (optional) | XML files or annotations |
| Result Mapping | Automatic from entity definitions | Manual XML/annotation mapping |
| Entities | Records/data classes with annotations | POJOs, manual mapping |
| Relationships | Automatic via @FK | Manual nested queries/joins |
| Type Safety | Compile-time checked | String SQL, typed result mapping |
| N+1 Problem | Prevented by design; requires explicit opt-in | Manual optimization |
| Transactions | Programmatic + @Transactional (Spring) | Manual or Spring @Transactional |
| Dynamic SQL | Kotlin/Java code | XML tags (<if>, <foreach>) |
| Learning Curve | Gentle; annotation-based | Moderate; XML knowledge helpful |
When to Choose Storm
- You want automatic entity mapping without XML and minimal boilerplate
- You prefer type-safe queries over string SQL
- You want relationships handled automatically
- You value compile-time safety
- You're starting a new project without legacy SQL