Skip to main content
Version: 1.11.0

Validation

Storm validates your entity and projection definitions at two levels: structural validation ensures your records follow the ORM's rules (valid primary key types, correct use of annotations, no circular dependencies), while schema validation compares your definitions against the actual database to catch mismatches before they surface as runtime errors.

Both levels are optional and configurable. Structural validation runs automatically on first use; schema validation must be explicitly enabled.


Record Validation

When Storm first encounters an entity or projection type, it inspects the record structure and validates that the definition is well-formed. This catches common modeling mistakes early, at startup rather than at query time.

What Gets Checked

Primary key rules:

  • The @PK type must be one of: boolean, int, long, short, String, UUID, BigInteger, Enum, or Ref. Floating-point types (float, double, BigDecimal) are rejected because they cannot reliably serve as identity values.
  • Compound keys (inline records annotated with @PK) follow the same type restrictions for each component.

Foreign key rules:

  • Fields annotated with @FK must be a Data type (entity, projection, or data class with a @PK) or a Ref wrapping such a type. Scalars like String or Integer cannot be foreign keys.
  • Auto-generated foreign keys (@FK(generation = ...)) cannot be inlined.

Inline component rules:

  • Fields annotated with @Inline must be record types. Scalars cannot be inlined.
  • Inline records must not declare their own @PK, since they are embedded within a parent entity.

Version fields:

  • At most one field per entity can be annotated with @Version. Multiple version fields are rejected.

Structural integrity:

  • Records must be immutable. Mutable fields (Kotlin var) are rejected.
  • Entities or projections that contain other entities or projections must annotate them as @FK or @Inline. Storm needs to know the relationship type to generate correct SQL.
  • The record graph is checked for cycles. If entity A inlines entity B, which inlines entity A, the circular dependency is reported.

Configuration

Record validation runs by default and causes startup to fail on the first error. The record-mode property controls this behavior:

ValueBehavior
failValidation errors cause startup to fail (default).
warnErrors are logged as warnings; startup continues.
noneRecord validation is skipped entirely.

This can be set as a system property, via StormConfig, or in Spring Boot's application.yml:

storm:
validation:
record-mode: fail # or "warn" or "none" (default: fail)

Schema Validation

Schema validation compares your entity and projection definitions against the actual database schema. It catches mismatches before they surface as runtime errors, similar to Hibernate's ddl-auto=validate. Storm never modifies the schema; it only reports mismatches.

What Gets Checked

CheckError KindSeverity
Table exists in the databaseTABLE_NOT_FOUNDError
Each mapped column exists in the tableCOLUMN_NOT_FOUNDError
Kotlin/Java type is compatible with the SQL column typeTYPE_INCOMPATIBLEError
Entity primary key columns match the database primary keyPRIMARY_KEY_MISMATCHError
@FK constraint references the correct target tableFOREIGN_KEY_MISMATCHError
Sequences referenced by @PK(generation = SEQUENCE) existSEQUENCE_NOT_FOUNDError
Numeric cross-category conversions (e.g., Integer mapped to DECIMAL)TYPE_NARROWINGWarning
Non-nullable entity field mapped to a nullable database columnNULLABILITY_MISMATCHWarning
Entity declares @PK but the database has no primary key constraintPRIMARY_KEY_MISSINGWarning
@UK field has a matching unique constraint in the databaseUNIQUE_KEY_MISSINGWarning
@FK field has a matching foreign key constraint in the databaseFOREIGN_KEY_MISSINGWarning

Errors indicate definitive mismatches that will cause runtime failures, such as missing tables or columns.

Warnings indicate situations where the mapping works at runtime but may involve subtle differences, such as precision loss when mapping a Kotlin Int to an Oracle NUMBER column. Warnings are logged but do not cause validation to fail (unless strict mode is enabled).

Constraint Validation

Schema validation checks that the database has the constraints your entity model declares. There are two categories of constraint findings:

Mismatches (errors) occur when a constraint exists in the database but contradicts the entity definition. For example, if @FK val city: City expects a foreign key referencing the city table, but the database has a foreign key on that column referencing the account table, that is a FOREIGN_KEY_MISMATCH. Similarly, if the entity declares @PK with columns (id) but the database primary key is (user_id, role_id), that is a PRIMARY_KEY_MISMATCH. Mismatches are always hard errors because they indicate a bug in the entity definition.

Missing constraints (warnings) occur when the database has no constraint at all for a declared @PK, @FK, or @UK field. These are warnings rather than errors because the ORM functions correctly without database-level enforcement: queries return the same results, inserts and updates succeed, and scrolling works as expected.

However, database constraints serve as a safety net that the application layer cannot replace:

  • Primary key constraints ensure row uniqueness at the database level. Without one, duplicate primary key values could be inserted by other applications or direct SQL.
  • Unique constraints protect against application bugs and concurrent modifications that could insert duplicate values. Without a database-level unique constraint, a @UK field might contain duplicates that go undetected until a findBy call unexpectedly returns multiple results.
  • Foreign key constraints protect referential integrity. Without a database-level foreign key constraint, orphaned rows can accumulate when referenced rows are deleted.
Suppressing Constraint Warnings

When the database intentionally omits a constraint (for performance, for views, or because integrity is enforced at the application level), use the constraint attribute to suppress the warning for that specific field:

// No FK constraint for performance reasons.
data class Order(
@PK val id: Int = 0,
@FK(constraint = false) val customer: Customer
) : Entity<Int>

// No unique index in the database.
data class User(
@PK val id: Int = 0,
@UK(constraint = false) val email: String
) : Entity<Int>

Setting constraint = false only suppresses the "missing" warning. If the database does have a constraint that contradicts the entity definition (a mismatch), it is always reported as a hard error regardless of this flag.

In strict mode, missing constraint warnings are promoted to errors, causing validation to fail. The constraint = false flag takes precedence: fields marked with it are excluded from validation even in strict mode.

Programmatic API

Any ORMTemplate created from a DataSource supports schema validation:

val orm = dataSource.orm

// Inspect errors programmatically
val errors: List<String> = orm.validateSchema()

// Or validate and throw on failure
orm.validateSchemaOrThrow()

Both methods have overloads that accept specific types to validate:

orm.validateSchema(User::class, Order::class)

The no-argument variants discover all entity and projection types on the classpath automatically.

On success, a confirmation message is logged at INFO level. On failure, each error is logged at ERROR level, and validateSchemaOrThrow() throws a PersistenceException with a summary of all errors. Warnings are always logged at WARN level regardless of the outcome.

Templates created from a raw Connection or JPA EntityManager do not support schema validation, since they lack the DataSource needed to query database metadata.

Strict Mode

By default, warnings (type narrowing and nullability mismatches) do not cause validation to fail. In strict mode, all findings are treated as errors:

val config = StormConfig.of(mapOf(VALIDATION_STRICT to "true"))
val orm = ORMTemplate.of(dataSource, config)
orm.validateSchemaOrThrow() // Warnings now cause failure

Suppressing Validation with @DbIgnore

Use @DbIgnore to suppress schema validation for specific entities or fields. This is useful for legacy tables, columns handled by custom converters, or known mismatches that are safe to ignore.

Suppress validation for an entire entity:

@DbIgnore
data class LegacyUser(
@PK val id: Int = 0,
val name: String
) : Entity<Int>

Suppress validation for a specific field:

data class User(
@PK val id: Int = 0,
val name: String,
@DbIgnore("DB uses FLOAT, but column only stores whole numbers")
val age: Int
) : Entity<Int>

The optional value parameter documents why the mismatch is acceptable. When @DbIgnore is placed on an inline component field, validation is suppressed for all columns within that component.

Custom Schemas

Schema validation respects @DbTable(schema = "..."). Each entity is validated against the schema specified in its annotation, or the connection's default schema if none is specified.

@DbTable(schema = "reporting")
data class Report(
@PK val id: Int = 0,
val name: String
) : Entity<Int>

Spring Boot Configuration

When using the Spring Boot Starter, both record and schema validation can be configured through application.yml:

storm:
validation:
record-mode: fail # or "warn" or "none" (default: fail)
schema-mode: none # or "warn" or "fail" (default: none)
strict: false # treat schema warnings as errors (default: false)

The schema-mode values:

ValueBehavior
noneSchema validation is skipped (default).
warnMismatches are logged at WARN level; startup continues.
failMismatches cause startup to fail with a PersistenceException.

Configuration Properties

PropertyDefaultDescription
storm.validation.record_modefailRecord validation mode: fail, warn, or none
storm.validation.schema_modenoneSchema validation mode: none, warn, or fail (Spring Boot only)
storm.validation.strictfalseWhen true, schema validation warnings are treated as errors