# 04 — Annotations vs. XML mappings in Hibernate 7.4.5 — what actually still works [← Previous: 03 — Inserting objects](03-inserting-objects.md) | [Next: 05 — JPA persistence annotations →](05-jpa-persistence-annotations.md) Backs [ankurm.com: Hibernate 7 annotations vs. XML mappings](https://ankurm.com/hibernate-annotations-vs-xml-mappings-making-the-right-choice-in-hibernate-7/). Post 4863 frames this as a two-way choice: annotations vs. `orm.xml`, with `hbm.xml` waved off as "deprecated." That framing undersells what actually happens when you boot Hibernate 7.4.5.Final with each of these on the classpath, and it misses that there are really **three** XML dialects in play, not two. Everything below was booted, not read about — see [`docs/output/mappingstyle-xml-vs-annotations.txt`](output/mappingstyle-xml-vs-annotations.txt) for the verbatim run, and the test classes under [`src/test/java/com/ankurm/hibernatedemo/mappingstyle/`](../src/test/java/com/ankurm/hibernatedemo/mappingstyle/) for the exact setup of each case. ## `hbm.xml` is not dead — it is a live code path with a warning label The Hibernate 6 story was: `hbm.xml` is deprecated, and `hibernate.transform_hbm_xml.enabled` is a shim that rewrites it into the modern model at boot. That shim setting still exists in 7.4.5 (`org.hibernate.cfg.MappingSettings.TRANSFORM_HBM_XML`, and the whole `org.hibernate.boot.jaxb.hbm.*` package plus an `HbmXmlTransformer` are present in `hibernate-core-7.4.5.Final.jar`), but it is **not required** to use `hbm.xml`. [`HbmXmlBootTest`](../src/test/java/com/ankurm/hibernatedemo/mappingstyle/HbmXmlBootTest.java) and [`HbmXmlRuntimeTest`](../src/test/java/com/ankurm/hibernatedemo/mappingstyle/HbmXmlRuntimeTest.java) put a real, annotation-free POJO ([`HbmEmployee`](../src/main/java/com/ankurm/hibernatedemo/mappingstyle/HbmEmployee.java)) on the classpath with only an [`.hbm.xml` mapping](../src/test/resources/com/ankurm/hibernatedemo/mappingstyle/HbmEmployee.hbm.xml), and boot a plain Hibernate `SessionFactory` three ways: - default settings (no `transform_hbm_xml.enabled` at all) — **boots successfully**, logs one WARN: `HHH90000028: Support for is deprecated ... migrate to orm.xml or mapping.xml, or enable hibernate.transform_hbm_xml.enabled for on the fly transformation` - `hibernate.transform_hbm_xml.enabled=true` — boots successfully, no observable behavior difference for a simple mapping - `hibernate.transform_hbm_xml.enabled=false` (explicit) — **also boots successfully** `HbmXmlRuntimeTest` goes further: it actually persists and loads an `HbmEmployee` row through the hbm.xml-only mapping, with the transform setting deliberately unset, and it round-trips correctly. So the honest 7.4.5 status of `hbm.xml` is: **it still fully works, unmodified, with a WARN-level deprecation notice** — not a shim you must opt into, not a hard failure, and not silently broken. The "transform" setting appears to matter for hbm.xml features that no longer have a native binding path in 7.x and must be rewritten into the modern model to be understood at all; a plain ``/``/`` mapping like this one never needs it. Treat any blog claim that `hbm.xml` "requires" the transform flag, or throws without it, as wrong for 7.4.5 — it is a live code path. (Chapter 14 hits the same "orm.xml still works with zero registration effort" theme from the named-query angle — see [`14 — Named queries`](14-named-queries.md#named-queries-in-ormxml).) ## `orm.xml` really can define an entire entity, annotation-free Post 4863's `orm.xml` example only *overrides* an already-annotated `Employee`. It never proves `orm.xml` can carry a mapping on its own. [`OrmXmlMappingResourcesTest`](../src/test/java/com/ankurm/hibernatedemo/mappingstyle/OrmXmlMappingResourcesTest.java) does: [`OrmXmlOnlyEntity`](../src/main/java/com/ankurm/hibernatedemo/mappingstyle/OrmXmlOnlyEntity.java) carries **zero** JPA/Hibernate annotations — not even `@Entity` — and its entire mapping lives in [`orm-xml-only-mapping.xml`](../src/test/resources/orm-xml-only-mapping.xml). The Spring Boot wiring that makes this work is `spring.jpa.mapping-resources` (confirmed via `javap` on `org.springframework.boot.jpa.autoconfigure.JpaProperties` in `spring-boot-jpa-4.1.1.jar` — note the package: Boot 4's autoconfigure split moved this off the old `org.springframework.boot.autoconfigure.orm.jpa` path entirely). With that one property set, Hibernate creates `xml_only_widgets`, and a JPQL query (`select o from OrmXmlOnlyEntity o ...`) against it succeeds — the entity name resolves purely from the XML. ## XML wins on conflict — confirmed, not just documented Post 4863's Q5 claims "XML always wins over annotations." [`AnnotationXmlOverrideTest`](../src/test/java/com/ankurm/hibernatedemo/mappingstyle/AnnotationXmlOverrideTest.java) proves it mechanically: [`OverrideEntity`](../src/main/java/com/ankurm/hibernatedemo/mappingstyle/OverrideEntity.java)`.value` is annotated `@Column(name = "annotation_name")`, and a matching [`orm-xml-override-mapping.xml`](../src/test/resources/orm-xml-override-mapping.xml) entry maps the same field to `xml_name`. Reading the runtime metamodel (`SessionFactoryImplementor.getMappingMetamodel().getEntityDescriptor(...).getPropertyColumnNames(...)`) confirms the live column name is `xml_name`, and a native query against that literal column name returns the persisted value. The article's claim is correct. ## `` is not an override switch — it is an annotation kill switch This is the sharpest correction. The natural assumption is that `` means "XML overrides annotations for the attributes XML mentions." [`XmlMappingMetadataCompleteTest`](../src/test/java/com/ankurm/hibernatedemo/mappingstyle/XmlMappingMetadataCompleteTest.java) shows it is stronger than that: with [metadata-complete set](../src/test/resources/orm-xml-metadata-complete.xml), `OverrideEntity`'s `@Id @GeneratedValue` on the `id` field is **entirely ignored**, even though the matching `orm.xml` entry never mentions `id` at all. Boot fails with `org.hibernate.AnnotationException: Entity '...OverrideEntity' has no identifier`. Metadata-complete does not selectively override — it switches off annotation processing for the whole persistence unit, and any attribute XML doesn't repeat is simply gone. ## The real "what XML can do that annotations can't" — a third XML dialect Post 4863 never mentions this, and it changes the shape of the whole topic: Hibernate 7 ships a **third** XML mapping format, distinct from both legacy `hbm.xml` and JPA-standard `orm.xml`. It lives in `org/hibernate/xsd/mapping/mapping-7.0.xsd` inside `hibernate-core-7.4.5.Final.jar`, under namespace `http://www.hibernate.org/xsd/orm/mapping`, and its own XSD documentation calls it out explicitly: *"XSD which 'extends' the JPA orm.xml XSD adding support for Hibernate specific features."* This is exactly the second migration target the `HHH90000028` deprecation warning names ("migrate to orm.xml **or mapping.xml**"). The difference is not cosmetic. Grepping both schemas for Hibernate-only concepts: | Element | `orm_3_2.xsd` (JPA-standard `orm.xml`) | `mapping-7.0.xsd` (Hibernate's native dialect) | |---|---|---| | `` | absent | present | | `` / `` / `` | absent | present | | ``-related elements | absent | present (11 occurrences) | [`MappingXmlNaturalIdTest`](../src/test/java/com/ankurm/hibernatedemo/mappingstyle/MappingXmlNaturalIdTest.java) proves this is not just schema noise: [`MappingXmlNaturalIdEntity`](../src/main/java/com/ankurm/hibernatedemo/mappingstyle/MappingXmlNaturalIdEntity.java) has **zero** annotations, its `` is declared purely in [`mapping-xml-natural-id.xml`](../src/test/resources/mapping-xml-natural-id.xml) (root element ``), and `session.byNaturalId(...).using("sku", ...).load()` resolves it correctly. So the accurate answer to "what can XML express that annotations can't" is actually inverted from how the article poses it: the JPA-portable `orm.xml` dialect can express **less** than annotations (no Hibernate extensions at all), while Hibernate's own `mapping.xml` dialect can express **anything annotations can**, including natural ids, formulas, and filters — you trade JPA portability for that power, not gain something unavailable to annotations. (Chapter 06 covers `@NaturalId` from the annotation side; this is the same feature expressed purely in XML.) ## Practical takeaway - Legacy `hbm.xml`: still boots and runs in 7.4.5, WARN-level deprecated, no forced migration. - `orm.xml` (JPA-standard): fully capable of defining an entity from scratch; wired into Spring Boot via `spring.jpa.mapping-resources`; portable across providers but capped at what `orm_3_2.xsd` can express — no Hibernate-only concepts. - Hibernate's native `mapping.xml` (`mapping-7.0.xsd`): the actual annotation-equivalent XML dialect, including `@NaturalId`, `@Formula`, and filters — not portable to other JPA providers, but not missing anything either. - XML (either dialect) always wins over a conflicting annotation for the same attribute. - `xml-mapping-metadata-complete` disables annotation processing for the *entire* persistence unit, not just the attributes the XML repeats — a much bigger blast radius than "override." [← Previous: 03 — Inserting objects](03-inserting-objects.md) | [Next: 05 — JPA persistence annotations →](05-jpa-persistence-annotations.md)