Metadata Mapping
Purpose
Decide where mapping information lives and what generates it, and keep it honest about the
schema it describes. Metadata mapping is what makes an ORM possible: the mapping is data,
read by a generic engine, instead of a per-class translation someone wrote. That is a large
win and it has three recurring costs — the metadata sits on the domain class, it is checked
late, and it drifts.
The choices
Annotations on the class the mapping lives with the code it maps.
Discoverable, but string values can drift; it couples
the class to the persistence framework.
External metadata (orm.xml) the class stays clean; the mapping is a
separate artefact that can vary per
deployment. Costs discoverability and is
not refactor-safe.
Programmatic configuration mapping built in code at startup (Spring
Data JDBC dialects, jOOQ, MyBatis, a
hand-written Data Mapper). Most explicit,
most verbose.
Generated code a build step produces the mapping or the
accessors from a source of truth — the
schema (jOOQ), the entities (JPA static
metamodel), or an interface (MapStruct).
Workflow
Inspect the JDK/compiler, JPA namespace/version, ORM/provider, annotation processors,
database dialect and schema deployment pipeline first. Examples are partial; this skill
does not authorize upgrading the stack to match its documentation sources.
- Pick the schema deployment authority. Versioned migrations should control shared
production schemas. Model-generated DDL can be reviewed into migrations; avoid independent
automatic startup mutation competing with the migration history.
- Decide whether the domain class may carry the metadata. This is the layering
question, and the honest answer depends on whether a separate domain model exists at all
(
data-source-patterns).
- Validate mapping/schema compatibility in CI and suitable startup environments. Strict
startup validation can intentionally reject mixed-version rolling deploys or restricted
production credentials; decide where it is safe and keep a pre-deploy compatibility gate.
- Prefer typed generated references where supported. Regeneration exposes removed/renamed
members at compile time when used. Remaining strings/constants need validation; moving a
string into a constant does not make its value schema-checked.
- Check for duplicated mapping. The same fact stated in annotations, in a migration,
in a DTO mapper and in a view is four places to update and three places to be wrong.
- Resist metadata-driven behaviour unless a stated driver requires it; see the decision
rules.
Decision rules
Entities are the persistence model, the team is small, the stack is JPA
→ annotations. The default, and the coupling is honest because
the class IS the persistence model.
A separate framework-free domain model exists
→ the metadata belongs on the persistence model (row/entity),
not on the domain type. If annotations are appearing on the
domain class, investigate the leak or an accepted coupling rather than
inferring that either model is redundant.
The same classes must map differently per deployment or per tenant
→ external metadata or programmatic configuration. This is the
case orm.xml was designed for and it is rare.
Column and attribute names appear as strings in queries or projections
→ use typed generated references where possible; validate remaining
string-based queries against the target mapping/schema.
The schema is the source of truth and is owned elsewhere
→ generate from a pinned schema (jOOQ-style), regenerate in CI and
compile consumers; test compatibility with the deployed schema too.
Mapping between two object shapes (entity ↔ DTO)
→ generated mapper, or explicit hand-written code. Reflection
-based deep mappers hide field mismatches until runtime.
Someone proposes storing the model definition as data so new fields
need no deploy
→ require the driver in writing. This buys deploy-free change
and moves validation and compatibility checks into a versioned
runtime schema/interpreter (enterprise-architecture-smells).
Rules
- Use one schema mutation authority. Spring Boot uses
spring.jpa.hibernate.ddl-auto;
Hibernate's native setting is hibernate.hbm2ddl.auto. Avoid update/create/create-drop
competing with migrations on shared data. Disposable test databases may deliberately
generate schemas; model-generated scripts reviewed into migrations are another valid path.
- Use
validate where startup failure is an acceptable control and permissions expose enough
metadata. For rolling deployment, validate both old and new application versions against the
expanded schema before rollout; do not discover incompatibility by replacing all healthy pods.
- Startup validation is not complete validation. It checks tables, columns and types; it
does not check nullability the way you would want, nor constraints, nor indexes, nor
defaults comprehensively across providers/dialects. A schema diff covers only the objects
and properties its extraction includes; exercise permissions, queries and writes separately.
- Annotations on domain classes are a real coupling and a defensible one. What is not
defensible is claiming a framework-free domain while the domain classes carry
@Entity — decide which architecture you have and record it
(layering-and-boundaries).
- Unchecked string literals naming columns or attributes are a runtime-failure risk. JPQL text,
Sort.by("cusotmerId"), projections by name, native queries: invalid names can fail at runtime, some
only on a rarely used path. Generate the JPA static metamodel and use Order_.CUSTOMER
style constants only when supplied by the chosen processor; they are not the portable
typed metamodel contract and string-consuming APIs still need execution/validation tests.
- Reflection/enhancement/accessor costs are provider, mapping and runtime specific. Metadata parsing
is primarily startup work, while field access, dirty checking and materialization remain hot-path
concerns. Do not infer significance; measure startup and query/allocation profiles
where it matters (
startup-cds-crac-leyden).
- Bytecode enhancement changes real behaviour, not just performance: lazy attribute
loading, inline dirty tracking, and support for lazy inverse
@OneToOne in applicable
Hibernate mappings. Feature flags, mutable types and provider/version matter; do not
assume all snapshots disappear or all associations become lazy. Enhancement has its own debugging cost — adopt it for a
named reason, not by default.
- Repeated facts can drift, but migration DDL, persistence mapping and API validation can
intentionally express different contracts. Identify which facts must agree; generate or
test those agreements instead of deleting independent constraints by counting repetitions.
- Metadata-driven models trade compile-time safety for deploy-free change, and the trade
includes an interpreter and runtime validation you must maintain. Versioned schemas,
bounded field/type limits and contract tests remain possible and necessary. Require a
concrete variability driver and confine dynamic behavior where practical
(
orm-structural-mapping on serialized LOB).
References
- Where mapping metadata lives — annotations, orm.xml,
programmatic and generated mappings compared on coupling, refactor safety,
discoverability and per-deployment variability; the mixed strategy that works
(annotations plus an override file); metamodel generation; and mapping between object
shapes. Read when choosing where the mapping should live, or when annotations are
accumulating somewhere they should not.
- Generation and drift — schema validation at startup
and what it does not catch, a schema diff in CI, generating code from the schema versus
generating the schema from code, bytecode enhancement's real effects, and the recurring
drift scenarios with their detection. Read when a mapping mismatch reached production, or
when setting up the build's guardrails.
1---2name: metadata-mapping3description: Expressing the object-to-schema mapping as metadata rather than hand-written code: where the mapping lives (annotations, external XML, programmatic), what reflection costs versus generated code, and how metadata drifts from the schema it describes. Use when persistence annotations accumulate on a domain class that is supposed to be framework-free, when the same mapping is expressed twice, when a schema change is discovered at runtime instead of at startup, when ddl-auto generates a schema in an environment that has migrations, when string literals name columns across the codebase, or when a fully metadata-driven model is proposed. Does not cover the mapping decisions themselves (orm-structural-mapping, inheritance-mapping-strategies) runtime ORM behaviour (orm-behavioral-patterns), or migrating from one mapping approach to another (architecture-refactoring-paths).4---56# Metadata Mapping78## Purpose910Decide where mapping information lives and what generates it, and keep it honest about the11schema it describes. Metadata mapping is what makes an ORM possible: the mapping is data,12read by a generic engine, instead of a per-class translation someone wrote. That is a large13win and it has three recurring costs — the metadata sits on the domain class, it is checked14late, and it drifts.1516## The choices1718```text19Annotations on the class the mapping lives with the code it maps.20 Discoverable, but string values can drift; it couples21 the class to the persistence framework.2223External metadata (orm.xml) the class stays clean; the mapping is a24 separate artefact that can vary per25 deployment. Costs discoverability and is26 not refactor-safe.2728Programmatic configuration mapping built in code at startup (Spring29 Data JDBC dialects, jOOQ, MyBatis, a30 hand-written Data Mapper). Most explicit,31 most verbose.3233Generated code a build step produces the mapping or the34 accessors from a source of truth — the35 schema (jOOQ), the entities (JPA static36 metamodel), or an interface (MapStruct).37```3839## Workflow4041Inspect the JDK/compiler, JPA namespace/version, ORM/provider, annotation processors,42database dialect and schema deployment pipeline first. Examples are partial; this skill43does not authorize upgrading the stack to match its documentation sources.44451. **Pick the schema deployment authority.** Versioned migrations should control shared46 production schemas. Model-generated DDL can be reviewed into migrations; avoid independent47 automatic startup mutation competing with the migration history.482. **Decide whether the domain class may carry the metadata.** This is the layering49 question, and the honest answer depends on whether a separate domain model exists at all50 (`data-source-patterns`).513. **Validate mapping/schema compatibility in CI and suitable startup environments.** Strict52 startup validation can intentionally reject mixed-version rolling deploys or restricted53 production credentials; decide where it is safe and keep a pre-deploy compatibility gate.544. **Prefer typed generated references where supported.** Regeneration exposes removed/renamed55 members at compile time when used. Remaining strings/constants need validation; moving a56 string into a constant does not make its value schema-checked.575. **Check for duplicated mapping.** The same fact stated in annotations, in a migration,58 in a DTO mapper and in a view is four places to update and three places to be wrong.596. **Resist metadata-driven behaviour** unless a stated driver requires it; see the decision60 rules.6162## Decision rules6364```text65Entities are the persistence model, the team is small, the stack is JPA66 → annotations. The default, and the coupling is honest because67 the class IS the persistence model.6869A separate framework-free domain model exists70 → the metadata belongs on the persistence model (row/entity),71 not on the domain type. If annotations are appearing on the72 domain class, investigate the leak or an accepted coupling rather than73 inferring that either model is redundant.7475The same classes must map differently per deployment or per tenant76 → external metadata or programmatic configuration. This is the77 case orm.xml was designed for and it is rare.7879Column and attribute names appear as strings in queries or projections80 → use typed generated references where possible; validate remaining81 string-based queries against the target mapping/schema.8283The schema is the source of truth and is owned elsewhere84 → generate from a pinned schema (jOOQ-style), regenerate in CI and85 compile consumers; test compatibility with the deployed schema too.8687Mapping between two object shapes (entity ↔ DTO)88 → generated mapper, or explicit hand-written code. Reflection89 -based deep mappers hide field mismatches until runtime.9091Someone proposes storing the model definition as data so new fields92need no deploy93 → require the driver in writing. This buys deploy-free change94 and moves validation and compatibility checks into a versioned95 runtime schema/interpreter (enterprise-architecture-smells).96```9798## Rules99100- **Use one schema mutation authority.** Spring Boot uses `spring.jpa.hibernate.ddl-auto`;101 Hibernate's native setting is `hibernate.hbm2ddl.auto`. Avoid `update/create/create-drop`102 competing with migrations on shared data. Disposable test databases may deliberately103 generate schemas; model-generated scripts reviewed into migrations are another valid path.104- Use `validate` where startup failure is an acceptable control and permissions expose enough105 metadata. For rolling deployment, validate both old and new application versions against the106 expanded schema before rollout; do not discover incompatibility by replacing all healthy pods.107- **Startup validation is not complete validation.** It checks tables, columns and types; it108 does not check nullability the way you would want, nor constraints, nor indexes, nor109 defaults comprehensively across providers/dialects. A schema diff covers only the objects110 and properties its extraction includes; exercise permissions, queries and writes separately.111- Annotations on domain classes are a real coupling and a defensible one. What is not112 defensible is claiming a framework-free domain while the domain classes carry113 `@Entity` — decide which architecture you have and record it114 (`layering-and-boundaries`).115- **Unchecked string literals naming columns or attributes are a runtime-failure risk.** JPQL text,116 `Sort.by("cusotmerId")`, projections by name, native queries: invalid names can fail at runtime, some117 only on a rarely used path. Generate the JPA static metamodel and use `Order_.CUSTOMER`118 style constants only when supplied by the chosen processor; they are not the portable119 typed metamodel contract and string-consuming APIs still need execution/validation tests.120- Reflection/enhancement/accessor costs are provider, mapping and runtime specific. Metadata parsing121 is primarily startup work, while field access, dirty checking and materialization remain hot-path122 concerns. Do not infer significance; measure startup and query/allocation profiles123 where it matters (`startup-cds-crac-leyden`).124- Bytecode enhancement changes real behaviour, not just performance: lazy attribute125 loading, inline dirty tracking, and support for lazy inverse `@OneToOne` in applicable126 Hibernate mappings. Feature flags, mutable types and provider/version matter; do not127 assume all snapshots disappear or all associations become lazy. Enhancement has its own debugging cost — adopt it for a128 named reason, not by default.129- Repeated facts can drift, but migration DDL, persistence mapping and API validation can130 intentionally express different contracts. Identify which facts must agree; generate or131 test those agreements instead of deleting independent constraints by counting repetitions.132- **Metadata-driven models trade compile-time safety for deploy-free change**, and the trade133 includes an interpreter and runtime validation you must maintain. Versioned schemas,134 bounded field/type limits and contract tests remain possible and necessary. Require a135 concrete variability driver and confine dynamic behavior where practical136 (`orm-structural-mapping` on serialized LOB).137138## References139140- [Where mapping metadata lives](references/metadata-sources.md) — annotations, orm.xml,141 programmatic and generated mappings compared on coupling, refactor safety,142 discoverability and per-deployment variability; the mixed strategy that works143 (annotations plus an override file); metamodel generation; and mapping between object144 shapes. Read when choosing where the mapping should live, or when annotations are145 accumulating somewhere they should not.146- [Generation and drift](references/generation-and-drift.md) — schema validation at startup147 and what it does not catch, a schema diff in CI, generating code from the schema versus148 generating the schema from code, bytecode enhancement's real effects, and the recurring149 drift scenarios with their detection. Read when a mapping mismatch reached production, or150 when setting up the build's guardrails.