Hibernate checks
The Hibernate panel runs a fixed, on-demand ruleset against your application's mapped JPA entities. It reads the JPA EntityManagerFactory metamodel, selected persistence-unit observations, and verified Spring Data JPA repository metadata where available. It never intercepts runtime queries, invokes repositories, executes SQL, or modifies mappings.
The checks are heuristic review prompts. They highlight common Hibernate and JPA performance and maintainability risks, but the right remediation still depends on your query patterns and data model.
Reading more than the preview
The ten-entry sampleViolations preview does not cap violationCount. View violations, or GET <api>/hibernate/rules/{id}/violations?scanId=...&offset=0&limit=100, retrieves the retained sequence, including persistence-unit labels, without re-reading entities or executing SQL. Retention is bounded across rules and units, so check truncated separately from evidence coverage. See snapshot, retention, and MCP/CLI retrieval.
Availability and bounds
The panel is available when Hibernate ORM and an EntityManagerFactory bean are present. If either is missing, or the metamodel cannot be read, BootUI returns a stable empty report with an explanatory status.
The scan covers the mapped entities your JPA metamodel reports, which includes entities added through @EntityScan or custom persistence-unit configuration, without scanning the whole classpath.
The active catalog contains 72 rules. Eight retired identifiers stay documented below so old links and persisted dismissals keep their meaning. Retired identifiers are never reused.
Evidence and incomplete scans
Factory settings belong to their persistence unit, not to whichever factory was discovered first. The advisor reads an allowlisted snapshot of effective Hibernate factory options where available and attributes findings to that unit. Effective values come from the factory's own SessionFactoryOptions and SQL statement logger, so a setting the application never configured reads as Hibernate's effective default (for example hibernate.use_sql_comments=false or a slow-query threshold of 0) rather than as unavailable evidence. Only a failed read is unknown.
Factory defaults do not establish per-session overrides, query-cache opt-in, entity-cache eligibility, or workload, and missing or unreadable evidence is not an observed false, zero, or framework default. Missing persistence-unit evidence qualifies usable known-findings scores without discarding findings from other units. See the shared score eligibility policy.
Repository checks need JPA provenance and unambiguous unit and domain attribution. Native queries, composed query annotations, projections, and entity graphs all affect applicability. Unsupported and ambiguous query shapes are not guessed, and Panache query methods stay outside the repository analysis.
Rule failures and unavailable required evidence produce an incomplete PARTIAL scan while retaining valid findings. The report's results list still contains findings only; the scan message summarizes coverage counts, and the report's diagnostics array lists every affected evaluation as {source, unit, level, message}: source is the rule id (or discovery for an unavailable factory, metamodel, repository metadata or unit attribution), unit is the persistence-unit label (application for application-wide rules), and level is ERROR for a failed rule, WARNING for unavailable evidence, or INFO when the only missing evidence is something this advisor does not observe by design (such as pool auto-commit guarantees or a provider-selected cache strategy). The message says whether the rule reached no conclusion, was partly evaluated with no findings, or reported findings from the evaluated part only, and names the missing evidence kinds with occurrence counts and up to three sanitized examples such as OrderRepository#findRecent or an entity name (for example query provenance, a JPQL shape outside the readable subset, or an unavailable unit setting). Messages use controlled phrases only and never carry query text, property values or exception messages. A failed evaluation is counted as failed, not also as unavailable evidence, and the skipped count excludes both. The array holds at most 200 entries: when more are produced, the last entry has source diagnostics and states how many were omitted, every rule and every distinct discovery reason keeps at least one entry (an ERROR when it has one), remaining slots go to further ERRORs and then round-robin by rule, and scan.message states "Diagnostics show M of N entries" while keeping the full counts. HIB-CONFIG-008 and HIB-CONFIG-011 gaps are reported at INFO level because they are advisor limits, but they still make the scan PARTIAL (see below). A rule that reports findings but could not evaluate every unit or query carries a coverageNote on its results entry (for example Incomplete in [default]: …) naming up to ten affected units and why, including units where the rule failed. Platform inapplicability is distinct from a failed applicable check, and rulesEvaluated counts distinct active rule attempts, not successful verification of every mapping.
Two things are not fully observable today: pool auto-commit guarantees (HIB-CONFIG-008) and effective cache access strategy (HIB-CONFIG-011). Where they apply, they and unsupported query-plan evidence can make an ordinary scan PARTIAL even when entity discovery succeeded. That is coverage disclosure, not a claim that the application is broken.
The declaration-based mapping checks are not a complete runtime mapping interpreter. XML overrides, auto-apply converters, custom generators, physical naming, runtime query changes, and some collection classifications cannot be reconstructed from annotations alone. The per-rule limitations below matter when you interpret both a finding and an absence of findings.
Violation locations
A finding about one entity, mapped attribute, or repository method carries a violation location: the entity class, the Java field or getter behind the attribute on its declaring class (a @MappedSuperclass included), or the repository interface method. The JPA metamodel and reflection record no line numbers, so these locations stay at MEMBER or CLASS precision and never show a line. During the explicit scan BootUI reads each located class's recorded source file name from its class file, without loading any class, and resolves its local source path through the Architecture advisor's module and source-set lookup. The class file is found through the class's own class loader. Quarkus serves bytecode-enhanced entities from memory, in dev and test mode alike, so BootUI then asks the same loader for every root of the entity's package and keeps the one local output directory that holds its class file. It keeps no path when no root or more than one root holds it, when an archive root holds a copy, when a root cannot be read, or when the package has more than 64 roots. At most 1,024 located classes are read per scan. Configuration, settings, and profile findings, and findings that span several elements such as composite identifier classes, carry no location. Unit-labelled samples and retained details keep the same location for the same finding across persistence units.
Version baseline
The audited baselines are Spring Boot 4.1.1 with Hibernate 7.4.5.Final, and Quarkus 3.33.3.1 with Hibernate 7.2.19.Final, both on Jakarta Persistence 3.2.0. BootUI now builds its Quarkus adapter against Quarkus 3.40.1, which manages Hibernate 7.4.9.Final. Your application may override these versions, and version-dependent conclusions use runtime evidence. Primary references and version caveats are listed at the end of this catalog.
Severity scale
| Severity | Meaning |
|---|---|
| CRITICAL | A configuration choice that can immediately damage production data. Currently emitted only by HIB-CONFIG-002, for destructive schema actions under a production-like profile. |
| HIGH | A mapping choice that commonly causes large performance surprises. |
| MEDIUM | A mapping or configuration issue that usually warrants review before production use. |
| LOW | Lower-impact hygiene findings. |
| INFO | An informational prompt where the fix depends heavily on project context. |
Quarkus and Jakarta create is create-only, unlike Hibernate hbm2ddl.auto=create.
The results panel lists only checks that found something, ordered by severity, finding count, and rule id. Each rule includes a few sample mapped members plus a remediation link.
The advisor score applies the shared severity penalty to every concrete finding, not once per violated rule. Dismissing a rule removes all of its findings from the score.
Filter the list, then jump to a check — the detail below narrows to match.
HIB-FETCH-001HIGHEager fetching should stay explicit and boundedHIB-FETCH-003HIGHCollection fetch joins should not be paged directlyHIB-FETCH-002INFOBatch fetching should cover lazy secondary-select associationsHIB-FETCH-005LOWEnhanced @Lob attributes should be loaded lazilyHIB-FETCH-006MEDIUMCollection associations should not declare @Fetch(JOIN)HIB-FETCH-007MEDIUMLazy basic attributes require bytecode enhancementHIB-FETCH-008INFOSubselect collection fetching should be reviewedHIB-ID-001MEDIUMReview IDENTITY insert-generation trade-offsHIB-ID-002MEDIUMReview table-based identifier allocationHIB-ID-003MEDIUMReview sequence allocation declarationsHIB-ID-004INFOReview provider-selected identifier strategiesHIB-ID-005LOWReview generated UUID strategy when index locality mattersHIB-ID-006HIGHGenerationType.IDENTITY disables JDBC batch insertsHIB-ID-007HIGHComposite identifier classes must satisfy the JPA contractHIB-MAP-001MEDIUMOne-to-many associations should be bidirectional or join-column basedHIB-MAP-002MEDIUMReview many-to-many list semanticsHIB-MAP-003MEDIUMEnum attributes should declare an explicit storage strategyHIB-MAP-004HIGHMany-to-many associations should not cascade removeHIB-MAP-005HIGHMany-to-one associations should not cascade removeHIB-MAP-006MEDIUMOne-to-one associations should prefer shared primary keysHIB-MAP-007INFOReview TABLE_PER_CLASS polymorphic queriesHIB-MAP-008MEDIUM@NotFound(IGNORE) should be reviewedHIB-MAP-009MEDIUMPersistent attributes should not be OptionalHIB-MAP-010MEDIUMReview element-collection list ordering and mutation costHIB-MAP-011INFOEntity classes should not be finalHIB-MAP-013INFOString columns should declare explicit lengthHIB-MAP-014LOWBigDecimal columns should declare precision and scaleHIB-MAP-015LOWDate/time attributes should use java.timeHIB-MAP-016LOW@ManyToOne should set optional=false when the join column is non-nullableHIB-MAP-018MEDIUMReview lazy inverse @OneToOne without enhancementHIB-MAP-020MEDIUMReview writable unidirectional one-to-many join-column DMLHIB-MAP-022INFOExplicit ordinal enum mappings should be reviewedHIB-MAP-023MEDIUMSet element collections of embeddables need equals and hashCodeHIB-MAP-024LOW@Lob on PostgreSQL stores values as large objectsHIB-ENTITY-001INFOEntities should override equals and hashCode consistentlyHIB-ENTITY-002MEDIUMVersionless optimistic locking should use dynamic updatesHIB-ENTITY-005LOWPersistent fields should not be publicHIB-ENTITY-006INFOReview primitive-version newness semanticsHIB-ENTITY-007MEDIUMReview assigned-ID Spring Data newnessHIB-ENTITY-008INFOMutable entities should declare @Version for optimistic lockingHIB-ENTITY-009INFOUnique business-key columns should consider @NaturalIdHIB-ENTITY-010INFOPrefer a numeric @Version over a timestampHIB-QUERY-001INFO@Modifying bulk queries should clear stale persistence contextHIB-QUERY-002INFOReview streaming query resource lifetimeHIB-QUERY-003INFONative Page queries should review count derivationHIB-QUERY-004MEDIUMDerived deleteBy methods load entities before deletionHIB-QUERY-005INFOEager to-one associations should be JOIN FETCHed in entity-returning queriesHIB-QUERY-006INFOPaged or streamed reads should prefer DTO projections over whole entitiesHIB-QUERY-007MEDIUMQueries should not JOIN FETCH more than one collectionHIB-QUERY-008MEDIUMBulk updates leave @Version unchangedHIB-CONFIG-003HIGHLazy loading outside transactions should stay disabledHIB-CONFIG-004INFOJDBC batching should be configured for writesHIB-CONFIG-005INFOJDBC batching should order inserts and updatesHIB-CONFIG-006INFOSlow query logging should be available in developmentHIB-CONFIG-007INFOHibernate statistics should be enabled when tuningHIB-CONFIG-008INFOConnection providers should disable auto-commit explicitlyHIB-CONFIG-009INFOCollection-parameter queries should use IN-clause paddingHIB-CONFIG-010HIGHQuery caching requires effective region supportHIB-CONFIG-011MEDIUMReview effective cache concurrency strategyHIB-CONFIG-002INFOSchema generation should not mutate non-test databasesHIB-CONFIG-012MEDIUMSQL logging should be off when a production profile is activeHIB-CONFIG-013LOWReview applicable JDBC temporal bindingHIB-CONFIG-014HIGHHibernate's built-in connection pool should not be usedHIB-CONFIG-015INFODeferred script initialization should have an intentional orderHIB-CONFIG-016INFOFail on pagination over collection fetchHIB-CONFIG-017LOWDisable SQL formatting in productionHIB-CONFIG-018HIGHBind-parameter logging should be off in productionHIB-CONFIG-019INFOSQL comments should be enabled intentionallyHIB-CONFIG-020INFOOracle JDBC fetch size should exceed the driver defaultHIB-CACHE-001INFOCached entity association coverage should be reviewedHIB-CACHE-002MEDIUMREAD_ONLY cache strategy on writable entities is unsafeHIB-CACHE-003INFOImmutable cached entities should use READ_ONLY
Fetching
HIB-FETCH-001 - Eager fetching should stay explicit and bounded
- Severity: HIGH
- Inspects:
@ManyToOne,@OneToOne,@OneToMany,@ManyToMany, and@ElementCollectionmappings on metamodel attributes. - Fires when: a mapping resolves to
FetchType.EAGER, including default-eager to-one associations where nofetchattribute is declared and element collections that explicitly opt into eager fetching. - Why it matters: eager mappings are fetched whether or not the use case needs them, which can amplify query counts, payload size, and accidental object graph or collection-table loading.
- Recommendation: prefer
LAZYmappings and fetch required data explicitly with joins, entity graphs, DTO projections, or targeted collection-value queries.
HIB-FETCH-003 - Collection fetch joins should not be paged directly
- Severity: HIGH
- Inspects: Spring Data JPA
@Querymethods with aPageableparameter and resolvable JPQLJOIN FETCHpaths. - Fires when: an attributable, supported paged JPA query fetch-joins a mapped collection and the known runtime or declared query hint establishes an in-memory pagination risk. An unknown runtime is not treated as an older version.
- Why it matters (Hibernate < 7.4): collection fetch joins duplicate root rows, and versions before 7.4 applied the
Pageablelimit in memory after loading the full, duplicated result set instead of at the SQL level. - Recommendation: page root identifiers first, then fetch the required collection graph in a second query inside the same transaction.
- Ownership: this rule alone reports the affected queries. HIB-CONFIG-016 reviews the unit-wide fail-fast guard once, without repeating these findings.
- Hibernate 7.4+: the "Limits and fetch joins" migration-guide entry documents that the limit for a paged query with a collection
JOIN FETCHis now applied in the generated SQL itself; theorg.hibernate.limitInMemoryquery hint restores the pre-7.4 in-memory behavior. The check does not certify SQL pushdown merely from the version: dialect/query-plan fallback remains possible, and an explicit true hint can bypass the ordinary pagination guard. Unknown query/runtime evidence is reported as a coverage gap, not a blanket warning or proof of safety.
HIB-FETCH-004 - Review entities with multiple bag collections
Retired. Multiple lazy bag declarations are valid; their existence does not establish simultaneous fetching. HIB-QUERY-007 reviews supported query shapes instead. A Java List without @OrderColumn does not itself prove effective BAG semantics because Hibernate can supply an implicit list index. Changing collections to sets also does not eliminate Cartesian multiplication when multiple collections are fetched together.
HIB-FETCH-002 - Batch fetching should cover lazy secondary-select associations
- Severity: INFO
- Inspects: lazy to-one and collection mappings, the owning factory's default batch-fetch size, association-level
@org.hibernate.annotations.BatchSize, and target-entity@BatchSizefor lazy to-one associations. - Fires when: a lazy association can initialize through secondary selects and no global or applicable local batch fetch size is detected.
- Why it matters: lazy associations without batch fetching can produce N+1 select patterns when the same association is traversed across multiple owner rows.
- Recommendation: set a bounded global batch-fetch size or targeted
@BatchSizefor associations traversed across multiple owners; use explicit fetch plans or paged/filtered queries for a single oversized collection. - Quarkus: effective factory options include Quarkus's integration default even when
quarkus.hibernate-orm.fetch.batch-sizeis absent from raw configuration. Named units are inspected separately.
HIB-FETCH-005 - Enhanced @Lob attributes should be loaded lazily
- Severity: LOW
- Inspects: persistent attributes annotated with
@Lobon bytecode-enhanced entities. - Fires when: enhancement is available and a materialized
@Lobattribute (for exampleStringorbyte[]) does not declare@Basic(fetch = FetchType.LAZY).java.sql.Blob,Clob, andNClobattributes are JDBC locators that are not materialized when the row is hydrated, so they are not reported. On a PostgreSQL unit, an attribute that HIB-MAP-024 reports is left to that rule, which recommends removing@Lobinstead. - Why it matters: infrequently used materialized CLOB/BLOB values can increase hydration cost. The benefit depends on payload size and access pattern, so this is an optimization prompt rather than a mapping defect.
- Recommendation: on enhanced entities, annotate infrequently accessed
@Lobfields with@Basic(fetch = FetchType.LAZY). - Enhancement requirement: Hibernate ORM 7 requires bytecode enhancement to honor lazy basic attributes. This rule deliberately does not recommend
@Basic(fetch = LAZY)when enhancement is unavailable; HIB-FETCH-007 instead reports existing ineffective lazy-basic declarations. The advisor relies on transformed-class evidence or an adapter-verified platform capability; an ordinary Hibernate enhancement setting alone does not prove that application classes were transformed. Quarkus provides that verified capability at build time.
HIB-FETCH-006 - Collection associations should not declare @Fetch(JOIN)
- Severity: MEDIUM
- Inspects: collection associations annotated with Hibernate's
@Fetch. - Fires when: a
@OneToManyor@ManyToManydeclares@Fetch(FetchMode.JOIN). - Why it matters: mapping-level joins can load more collection state than a use case needs. They do not force every JPQL query to join: query fetching directives determine that path. The declaration does not prove an actual Cartesian product or pagination failure.
- Recommendation: prefer
@Fetch(FetchMode.SELECT), an explicit entity query, or a DTO projection, and requestJOIN FETCHonly in the specific queries that need the graph.
HIB-FETCH-007 - Lazy basic attributes require bytecode enhancement
- Severity: MEDIUM
- Inspects: attributes declaring
@Basic(fetch = FetchType.LAZY)and the owning entity's enhancement state. - Fires when: a lazy basic attribute belongs to an entity whose relevant enhancement is known to be absent. Unavailable enhancement evidence is not treated as verified absence.
- Why it matters: without enhancement, Hibernate cannot intercept access to a basic field and the requested lazy column loading is ineffective.
- Recommendation: enable Hibernate bytecode enhancement, or remove the misleading
LAZYdeclaration.
HIB-FETCH-008 - Subselect collection fetching should be reviewed
- Severity: INFO
- Inspects: collection associations declaring
@Fetch(FetchMode.SUBSELECT). - Fires when: a mapped
@OneToManyor@ManyToManyuses subselect fetching. - Why it matters: initialization can load the same collection role for the originating registered query/load group, not necessarily every owner in the persistence context. A large owner group can fetch more data than the caller needs.
- Recommendation: keep
SUBSELECTonly for bounded owner sets. Prefer an explicit entity query or DTO projection when owner or collection cardinality can be large.
Identifiers
HIB-ID-001 - Review IDENTITY insert-generation trade-offs
- Severity: MEDIUM
- Inspects: mapped attributes annotated with
@GeneratedValue. - Fires when: the generator strategy is
GenerationType.IDENTITY. - Why it matters: identity columns require the insert to execute immediately so Hibernate can read the generated key, which disables JDBC batch inserts for those entities.
- Recommendation: prefer
SEQUENCEwith an allocation size and Hibernate pooled optimizer when the database supports sequences and insert throughput justifies it. IDENTITY is a supported choice, not a mapping defect. When HIB-ID-006 provides the stronger batching-specific finding, this generic advice is not counted again.
HIB-ID-002 - Review table-based identifier allocation
- Severity: MEDIUM
- Inspects: mapped attributes annotated with
@GeneratedValue. - Fires when: the generator strategy is
GenerationType.TABLE. - Why it matters: table generators emulate sequences through row updates, which can serialize identifier allocation under concurrent inserts.
- Recommendation: compare generator and pooling choices against the database and insert workload. The declaration does not demonstrate contention, and pooled table allocation may be intentional.
HIB-ID-003 - Review sequence allocation declarations
- Severity: MEDIUM
- Inspects: field-level and class-level
@SequenceGeneratordeclarations. - Fires when:
allocationSize=1. - Why it matters: when this declaration selects the active generator, an allocation size of 1 cannot pool sequence values. An unused or overridden declaration does not establish runtime allocation behavior.
- Recommendation: measure allocation overhead before increasing the size; coordinate the optimizer, database sequence increment and other writers. The advisor does not resolve every named, package-level or custom generator.
HIB-ID-004 - Review provider-selected identifier strategies
- Severity: INFO
- Inspects: identifier attributes annotated with
@GeneratedValue, excludingUUID-typed identifiers. - Fires when:
strategyis omitted (or set toAUTO). - Why it matters:
AUTOdeliberately delegates generator selection to the provider. Hibernate normally uses sequence-style generation for numeric identifiers, with a table fallback where sequences are unavailable, but named/custom generators can affect resolution. Explicit AUTO is legal and is not equivalent to IDENTITY. - Recommendation: review the selected generator when portability or allocation throughput matters. Retain AUTO when its behavior fits the application; choosing an explicit strategy is not universally required.
- UUID identifiers: skipped entirely - for a
UUID-typed identifier, Hibernate 6 interpretsAUTOas theUuidGenerator, not a sequence, so this rule's "sequence-or-table fallback" rationale does not apply. HIB-ID-005 owns the UUID case exclusively, so aUUID @Id @GeneratedValuewith no explicit strategy is flagged only once, by HIB-ID-005, with UUID-specific guidance instead of a misleading sequence/table warning. - Quarkus/Panache: skipped for the
idfield inherited as-is from Panache's ownPanacheEntity(ORM or Reactive), which declares@Id @GeneratedValue public Long id;with no explicit strategy and cannot be annotated by the application. A custom identifier the application declares itself (including on aPanacheEntityBasesubclass) is still checked normally.
HIB-ID-005 - Review generated UUID strategy when index locality matters
- Severity: LOW
- Inspects:
UUIDidentifier attributes annotated with@GeneratedValue. - Fires when: the attribute is not also annotated with
@UuidGenerator. - Why it matters: default random UUID generation can affect index locality, depending on the database and workload. This declaration-level check does not establish an effective custom generator or measured index fragmentation.
- Recommendation (Hibernate 7.0+): annotate the identifier with
@UuidGenerator(style = VERSION_7)(VERSION_6is also acceptable). Both styles are monotonic, index-friendly UUID variants and only exist starting in Hibernate 7.0 (both@Incubating) - confirmed by diffingUuidGenerator.javabetween the 6.6 branch (onlyAUTO/RANDOM/TIME) and the 7.0 branch (addsVERSION_6/VERSION_7). TIMEcaveat:style = TIMEproduces an RFC 4122 version 1 UUID (per@UuidGenerator's own Javadoc: "time-based generation strategy consistent with RFC 4122 version 1, but with IP address instead of MAC address"), which places the fast-changingtime_lowfield first and is not materially more index-friendly than a random (v4) UUID - it does not yield the monotonic ordering that makesVERSION_6/VERSION_7genuinely index-friendly, so do not treatTIMEas a fix.- Generator caveat:
@UuidGeneratorwithout a style still defaults to AUTO/RANDOM; its presence does not prove ordered UUIDs. String-based, named and custom generators are not fully resolved by this check. - Version handling: the check does not read the runtime Hibernate version; every supported runtime uses ORM 7, where
VERSION_6/VERSION_7are available.
HIB-ID-006 - GenerationType.IDENTITY disables JDBC batch inserts
- Severity: HIGH
- Inspects: the owning factory's effective JDBC batch size and identifier attributes annotated with
@GeneratedValue. - Fires when: the effective JDBC batch size is greater than 1 and an identifier uses
GenerationType.IDENTITY. - Why it matters: Hibernate must execute each identity insert immediately so it can read back the generated key, preventing JDBC insert batching for those entities despite the configured batch size.
- Recommendation: switch IDENTITY identifiers to
SEQUENCEwith a pooledallocationSizeso Hibernate can batch inserts, or drop the JDBC batch-size expectation for these entities.
HIB-ID-007 - Composite identifier classes must satisfy the JPA contract
- Severity: HIGH
- Inspects:
@EmbeddedIdattribute types and@IdClassvalues (including inherited from a superclass). - Fires when: a non-record class has no public or protected no-arg constructor; or the class does not override both
equalsandhashCode. - Why it matters: a composite identifier needs stable equality and a construction path that JPA can use. A record supplies its canonical constructor and generated equality methods, so it is exempt from the no-arg-constructor check.
- Recommendation: for a non-record class, declare a public or protected no-arg constructor and override both
equalsandhashCodeover every identifier field. - Persistence 3.2: Serializable is not a composite-key requirement. This check verifies detectable structure, not whether an equality implementation actually compares the correct identifier state.
- Quarkus/Panache: applies identically - this check inspects only the identifier class via reflection, with no framework-specific behavior.
Mapping
HIB-MAP-001 - One-to-many associations should be bidirectional or join-column based
- Severity: MEDIUM
- Inspects:
@OneToManymappings. - Fires when: a one-to-many association has neither
mappedBynor@JoinColumn/@JoinColumns. - Why it matters: Hibernate models that shape through a join table by default, which often produces extra DML and a less obvious schema.
- Recommendation: prefer a bidirectional association with
@ManyToOneon the child and@OneToMany(mappedBy=...)on the parent so the child's foreign key owns the relationship. If a unidirectional mapping is intentional, add@JoinColumnto drop the join table and review the extra update statements flagged by HIB-MAP-020.
HIB-MAP-002 - Review many-to-many list semantics
- Severity: MEDIUM
- Inspects:
@ManyToManymappings and their Java collection type. - Fires when: a many-to-many association is declared as
Listwithout an explicit persistent order column. An explicitly indexed ordered list is not reported merely for using List. - Why it matters: collection semantics affect link-table mutation costs. Hibernate's effective default list semantics and XML mappings can also provide indexing; this declaration-only check does not resolve every case.
- Recommendation: preserve domain ordering when required. Benchmark mutation behavior; consider Set only when its uniqueness semantics fit, or a link entity when the relationship has its own attributes or lifecycle.
HIB-MAP-003 - Enum attributes should declare an explicit storage strategy
- Severity: MEDIUM
- Inspects: enum-valued mapped attributes.
- Fires when: an enum attribute lacks an observed storage declaration after supported explicit conversion and
@EnumeratedValueexclusions. Absence of a member annotation alone does not establish effective ordinal storage. - Why it matters: positional ordinal persistence couples stored values to enum order. Persistence 3.2 can infer STRING storage from a String
@EnumeratedValue, and auto-apply/class-level/XML converters can replace default storage. Full converter resolution remains outside this rule's evidence. - Recommendation: use
@Enumerated(EnumType.STRING), a database-native enum type, or an explicit converter with stable database codes.
HIB-MAP-004 - Many-to-many associations should not cascade remove
- Severity: HIGH
- Inspects:
@ManyToManycascade settings. - Fires when: the cascade list contains
REMOVEorALL. - Why it matters: many-to-many targets usually have independent lifecycles, so delete cascades can remove shared rows instead of only join-table links.
- Recommendation: remove
REMOVE/ALL; usePERSIST/MERGEonly when needed, or model the join table as an entity.
HIB-MAP-005 - Many-to-one associations should not cascade remove
- Severity: HIGH
- Inspects:
@ManyToOnecascade settings. - Fires when: the cascade list contains
REMOVEorALL. - Why it matters: child-to-parent delete cascade can remove a parent shared by other children.
- Recommendation: cascade lifecycle operations from aggregate roots to owned children, not from children to parents.
HIB-MAP-006 - One-to-one associations should prefer shared primary keys
- Severity: MEDIUM (LOW when no lifecycle-dependency signal is detected)
- Inspects: owning-side
@OneToOnemappings. - Fires when: the association has no
mappedBy, no@MapsId, and the association itself is not the identifier. The finding is MEDIUM when the mapping looks lifecycle-dependent (optional=falseor cascadeREMOVE/ALL) and LOW otherwise. - Why it matters: dependent one-to-one rows often share the parent lifecycle and can avoid an extra foreign-key/index pair by sharing the primary key.
- Recommendation: use
@MapsIdwhen the child row is lifecycle-dependent on the parent; keep a separate foreign key only when the model requires independent identity.
HIB-MAP-007 - Review TABLE_PER_CLASS polymorphic queries
- Severity: INFO
- Inspects: class-level
@Inheritance. - Fires when: the strategy is
InheritanceType.TABLE_PER_CLASS. - Why it matters: polymorphic queries over the base type require a
UNIONacross concrete subtype tables. - Recommendation: evaluate the polymorphic query workload before changing inheritance strategy. Subtype-only access can make TABLE_PER_CLASS intentional; the declaration does not prove expensive queries are executed.
HIB-MAP-008 - @NotFound(IGNORE) should be reviewed
- Severity: MEDIUM
- Inspects: Hibernate
@NotFoundannotations. - Fires when:
action=IGNORE. - Why it matters: missing target rows are silently treated as null and the association must be resolved eagerly to know whether the target exists.
- Recommendation: repair referential integrity or model optional data explicitly instead of suppressing missing targets.
HIB-MAP-009 - Persistent attributes should not be Optional
- Severity: MEDIUM
- Inspects: mapped attributes whose raw Java type is
java.util.Optional. - Fires when: an
Optionalfield or property is part of the mapped model. - Why it matters: Optional is not a standard portable persistent basic type. A custom type or converter can make a mapping valid; raw Java type alone does not prove a broken runtime mapping.
- Recommendation: map the underlying nullable type and expose
Optionalfrom a non-persistent getter if desired.
HIB-MAP-010 - Review element-collection list ordering and mutation cost
- Severity: MEDIUM
- Inspects:
@ElementCollectionattributes typed asList. - Fires when: the list does not declare
@OrderColumn- regardless of whether it declares@OrderBy. - Why it matters: unindexed bag-like collections can need broad delete/reinsert work for some mutations. The effective collection classification and mutation determine the cost; absent
@OrderColumndoes not prove every change rewrites every row, especially with Hibernate's implicit list-index support. - Recommendation: use an order column when persistent positional order is required, or Set when its semantics fit.
@OrderByprovides read-time sorting, not an index. Do not combine@OrderByand@OrderColumnas a generic fix.
HIB-MAP-011 - Entity classes should not be final
- Severity: INFO
- Inspects:
@Entityclasses for thefinalmodifier and their bytecode-enhancement state. - Fires when: an entity is declared
finaland is not proven bytecode-enhanced. - Why it matters: final entities are not Jakarta-portable and cannot use subclass proxies for lazy to-one associations. Bytecode enhancement is a valid alternative and is not itself blocked by a final declaration.
- Recommendation: prefer non-final entities (and
openKotlin entities) when lazy subclass proxies are needed. Bytecode-enhanced entities are exempt. - Kotlin note: Kotlin classes are final by default. Before Kotlin 2.3.20 the JPA plugin supplied no-arg constructors but did not itself enable all-open; since 2.3.20 JPA setup enables both. Boot's managed Kotlin 2.3.21 and Quarkus 3.40's 2.4.10 are both at or above 2.3.20, but an application can pin an older Kotlin plugin. The emitted class, not dependency or plugin-name presence, determines finality.
HIB-MAP-012 - SINGLE_TABLE inheritance should declare @DiscriminatorColumn
Retired. Jakarta Persistence defines a valid implicit discriminator column. Requiring an explicit annotation solely for visibility scores a supported default without evidence of an incorrect mapping.
HIB-MAP-013 - String columns should declare explicit length
- Severity: INFO
- Inspects: persistent
Stringattributes (excluding identifiers and@Lobfields). - Fires when: there is no
@Columnmapping and nocolumnDefinition. Reflection cannot distinguish@Column(length = 255)from the annotation's default length, so an existing@Columnis treated as an explicit mapping. - Why it matters: default generated string DDL may not match domain expectations. Annotation absence does not prove the physical column length: migrations, validation integration, converters and XML overrides can determine it.
- Recommendation: set
@Column(length=...)to match the domain, or use@Lob/columnDefinitionfor free-text payloads.
HIB-MAP-014 - BigDecimal columns should declare precision and scale
- Severity: LOW
- Inspects: persistent
BigDecimalattributes. - Fires when:
@Column(precision=..., scale=...)is missing or precision is zero. - Why it matters: when Hibernate generates the schema, it falls back to its dialect defaults, commonly
numeric(38,2), so stored values can be rounded to two decimals. The annotation does not affect a column created by a migration, and Hibernate does not round bound values at runtime because the annotation is missing. Effective converters, column definitions and validation metadata are not fully resolved. - Recommendation: declare the precision and scale the domain needs, especially when Hibernate generates DDL or a tool derives migrations from the mapping. A mapping annotation is not input validation.
HIB-MAP-015 - Date/time attributes should use java.time
- Severity: LOW
- Inspects: persistent attributes typed as
java.util.Date,java.util.Calendar, orjava.sqltemporal types. - Fires when: any of those legacy types is detected on a mapped attribute.
- Why it matters: older temporal types have mutable or less expressive APIs. Persistence 3.2 deprecates
@Temporalin favor of modern types, but the correct replacement must preserve date, wall-clock, instant and offset semantics. - Recommendation: migrate to
java.time(Instant,LocalDate,LocalDateTime,OffsetDateTime,ZonedDateTime) where semantically equivalent. Not every java.time type carries a zone.
HIB-MAP-016 - @ManyToOne should set optional=false when the join column is non-nullable
- Severity: LOW
- Inspects:
@ManyToOneassociations and their@JoinColumn. - Fires when:
@JoinColumn(nullable=false)is set butoptional=falseis not. - Why it matters: the object-level optionality declaration and column-nullability declaration convey inconsistent intent. This does not prove a secondary SELECT occurs, and neither declaration alone verifies physical constraints.
- Recommendation: set
@ManyToOne(optional=false)whenever the join column is non-nullable.
HIB-MAP-017 - Lazy owning @OneToOne requires bytecode enhancement
Retired. An owning one-to-one holds the foreign key, so Hibernate can create a proxy without enhancement; the check reported a supported lazy mapping. HIB-MAP-018 covers the inverse side, where enhancement does matter.
HIB-MAP-018 - Review lazy inverse @OneToOne without enhancement
- Severity: MEDIUM
- Inspects: non-owning (
mappedBy)@OneToOneassociations and whether the declaring entity is bytecode enhanced. - Fires when: relevant enhancement is known absent and an entity declares an explicitly lazy non-owning
@OneToOne. Eager declarations and unknown enhancement do not establish a failed lazy-loading request. - Why it matters: determining whether the inverse row exists can prevent the expected lazy behavior. Query plans, optionality, cache state and access patterns determine actual queries; this declaration does not prove N+1.
- Recommendation: enable bytecode enhancement, or replace the bidirectional
@OneToOnewith a shared primary key (@MapsId) and a unidirectional mapping. - Quarkus: bytecode enhancement is verified by the adapter's unconditional build-time entity enhancement, so this check never fires there.
HIB-MAP-019 - Missing foreign key indexes
Retired. Annotation declarations cannot verify physical index coverage, and explicit annotation names can still be rewritten by a physical naming strategy. Database structural checks own index evidence; the Hibernate advisor does not duplicate that work by assuming absent @Index means absent database index.
HIB-MAP-020 - Review writable unidirectional one-to-many join-column DML
- Severity: MEDIUM
- Inspects: unidirectional
@OneToManyassociations and their join-column annotations. - Fires when: a
@OneToManyhas nomappedBy, declares a join column, and that join column is not read-only (insertable=false, updatable=false). Composite@JoinColumnsis exempt only when every join column is read-only. - Why it matters: this ownership shape can require separate foreign-key UPDATE work depending on the effective mapping and mutation. The declaration alone does not prove an exact INSERT/UPDATE sequence.
- Recommendation: make the association bidirectional with
@ManyToOneon the child and@OneToMany(mappedBy=...)on the parent so the child's foreign key is written in theINSERT. A read-only@JoinColumn(insertable=false, updatable=false)is exempt.
HIB-MAP-021 - Legacy @Where restrictions should be migrated
Retired. Hibernate ORM 7.0 removed @Where and @WhereJoinTable, and every supported runtime uses ORM 7 (Spring Framework 7 requires ORM 7.1 or later; Quarkus 3.40 ships 7.4). Code cannot compile against the removed types, and an annotation whose type is missing at runtime is silently dropped by reflection, so the check could never fire. Migrate any remaining restrictions to @SQLRestriction, @SQLJoinTableRestriction, or @SoftDelete as part of the ORM 7 upgrade.
HIB-MAP-022 - Explicit ordinal enum mappings should be reviewed
- Severity: INFO
- Inspects: enum attributes declaring
@Enumerated(EnumType.ORDINAL). - Fires when: ordinal storage is explicit, so the mapping is intentional but still carries a schema-evolution risk.
- Why it matters: positional ordinal storage changes meaning when enum constants are inserted or reordered. Persistence 3.2 also permits stable numeric codes through
@EnumeratedValue; numeric storage is not always position. - Recommendation: prefer
STRING, a database-native enum, or a stable-code converter. KeepORDINALonly when append-only ordering is an explicit schema contract.
HIB-MAP-023 - Set element collections of embeddables need equals and hashCode
- Severity: MEDIUM
- Inspects:
@ElementCollectionattributes declared asSet(notSortedSet) and their@Embeddableelement class, resolved from the generic type ortargetClass. - Fires when: the element class is neither a record nor
@Immutableand does not override bothequals(Object)andhashCode()anywhere belowObject. Attributes marked@Immutableor using a custom@CollectionTypeare skipped, and an unresolvable element type is reported as missing evidence rather than as clean. - Why it matters: Hibernate snapshots a
Setcollection as copies of its elements and looks each current element up in that snapshot. With identity equality no lookup matches, so the collection is treated as changed whenever the owner is flushed: Hibernate deletes every collection row and inserts them all again, andSet.removewith an equal value never matches. Sorted sets compare through their comparator and are not affected. - Recommendation: implement
equalsandhashCodeover the embeddable's value state, or model it as a record.
HIB-MAP-024 - @Lob on PostgreSQL stores values as large objects
- Severity: LOW
- Inspects:
@Lobattributes typed asString,char[],Character[],byte[], orByte[], in persistence units whose observed dialect is PostgreSQL (or a subclass such as PostgresPlus, but not Spanner's PostgreSQL interface). - Fires when: the attribute keeps the default LOB binding. An explicit
@JdbcTypeCodewith a non-LOB type such asSqlTypes.LONG32VARCHARis exempt; a converter,@JdbcType, or@Typeis reported as missing evidence. AcolumnDefinitiondoes not exempt the attribute because it changes the DDL, not the binding. JDBCBlob/Cloblocators are intentional large-object handles and are not reported. When the dialect is unknown and@Lobattributes exist, the rule reports missing evidence. - Why it matters: Hibernate's PostgreSQL dialect maps
@Lobto anoidcolumn and binds values through the large-object API. The content lives inpg_largeobject, so deleting or updating the row leaves the old large object behind until it is unlinked, and atextorbyteacolumn created by a migration does not match that binding. - Recommendation: remove
@Lobunless large-object storage is intended: map long text with@JdbcTypeCode(SqlTypes.LONG32VARCHAR)(text) and binary data as a plainbyte[](bytea). Keep@Lobfor deliberate large-object storage, with thelo_managetrigger orvacuumlocleanup. On PostgreSQL units, HIB-FETCH-005 does not also report the attributes this rule covers.
Entity design
HIB-ENTITY-001 - Entities should override equals and hashCode consistently
- Severity: INFO
- Inspects: entity classes for detectable
equals(Object)andhashCode()overrides. - Fires when: an entity overrides one method but not the other.
- Why it matters: inconsistent equality contracts break sets, maps, and Hibernate collection semantics.
- Recommendation: implement
equalsandhashCodeas a pair, and review generated identifier semantics before using entities in hash-based collections.
HIB-ENTITY-002 - Versionless optimistic locking should use dynamic updates
- Severity: MEDIUM
- Inspects: Hibernate
@OptimisticLocking. - Fires when:
type=DIRTYortype=ALLis used without@DynamicUpdate. - Why it matters: versionless optimistic locking relies on update predicates that match the chosen locking strategy.
- Recommendation: add
@DynamicUpdatewhen using versionless optimistic locking, or use a regular@Versioncolumn for simpler optimistic locking.
HIB-ENTITY-003 - equals/hashCode should not include lazy associations
Retired. The existence of equality overrides and associations does not show that the methods traverse associations. Safe ID-only implementations also matched. HIB-ENTITY-001 still checks an inconsistent equals/hashCode pair; it does not claim to validate method bodies.
HIB-ENTITY-004 - toString should not include lazy associations
Retired. A toString override plus association declarations does not establish traversal. No method-body analysis proves lazy loading here; safe ID-only implementations must not receive this finding.
HIB-ENTITY-005 - Persistent fields should not be public
- Severity: LOW
- Inspects: entity attributes backed by a
java.lang.reflect.Fieldthat ispublic. - Fires when: a field-access persistent field is
public. A field annotated@Transientis never flagged - it is not written to the database, so it carries none of the accessor-bypass risk this check targets (for example a public@Transientflag field used by a hand-writtenisNew()/Persistableimplementation). Property-access (getter-mapped) attributes are never flagged either, even when the underlying getter method ispublic- the metamodel resolves those attributes from aMethod, not aField, and public getters are the normal, fully JPA/Hibernate-instrumented way to expose a property-access attribute. - Why it matters: public fields can weaken encapsulation and proxy-based interception. Bytecode enhancement can instrument field access; a public field is not proof that Hibernate dirty checking is broken.
- Recommendation: keep persistent fields private (or package-private) and expose mutators when needed; this preserves application encapsulation without claiming that visibility alone establishes enhancer behavior.
- Kotlin note: a
lateinit varis exempt. Kotlin has no public fields — the property is read and written through its generatedgetX/setXpair — but the compiler must leave the backing field public so the initialisation check can run from outside the class, and the language offers no way to change that. A property the author did expose, written@JvmField var, generates no accessors at all and is still reported: that is the same encapsulation break a Java public field is. - Quarkus/Panache: skipped with verified adapter-provided Panache transformation capability, not merely an unrelated Panache class on the runtime classpath. Panache's active-record entities are meant to be used with public fields; once a Panache extension is present, its build-time bytecode transformation rewrites every public field access on any Hibernate-managed class (not just
PanacheEntityBasesubclasses) into the matching getter/setter call, so the accessor-bypass concern this check targets does not apply.
HIB-ENTITY-006 - Review primitive-version newness semantics
- Severity: INFO
- Inspects: primitive-version declarations in the supported Spring Data JPA newness context.
- Fires when: a verified standard Spring Data JPA repository domain entity that does not implement
Persistabledeclares a primitive@Versionattribute. - Why it matters: primitive identifiers and versions are legal. Spring Data recognizes zero-valued numeric primitive identifiers as new, but cannot use a primitive version as a nullable newness signal and falls back to identifier inspection. A wrapper alone does not prove correct custom newness or equality behavior.
- Recommendation: review the repository's actual save/persist/merge policy when nullable version detection is needed. Do not replace valid primitive IDs merely to satisfy a generic Hibernate warning.
- Quarkus/Panache: Spring Data newness advice is inapplicable without verified JPA repository metadata.
HIB-ENTITY-007 - Review assigned-ID Spring Data newness
- Severity: MEDIUM
- Inspects: Spring Data repository domain entities with assigned identifiers (lacking
@GeneratedValue) and no nullable@Versionattribute. - Fires when: a discovered Spring Data repository domain entity does not implement
org.springframework.data.domain.Persistable, including through inherited subinterfaces, within the supported default newness policy. A primitive Version does not supply a nullable signal. Unresolved custom behavior is unknown. - Why it matters: with populated assigned identifiers, the standard save policy can choose merge rather than persist. Actual SELECT/INSERT behavior depends on entity state and provider execution; a query before every insert is not guaranteed.
- Recommendation: consider a nullable version, a correctly managed
Persistable<ID>lifecycle, or another deliberate persistence policy. Incorrect customisNew()handling can itself cause errors; implementing Persistable is not universally required. - Quarkus: skipped entirely because no Spring Data repository metadata is available. The concern is specific to Spring Data's repository
save()merge-vs-persist decision; Panache's ownpersist()/persistAndFlush()always issues anINSERTand never probes existence first, so there is nothing to recommend on a Quarkus/Panache app.
HIB-ENTITY-008 - Mutable entities should declare @Version for optimistic locking
- Severity: INFO
- Inspects: mapped entities that carry non-identifier persistent state (at least one attribute that is not an
@Id,@EmbeddedId,@Version, association, or@Transient). - Fires when: the entity declares no
@Versionattribute and has not opted into versionless optimistic locking (@OptimisticLocking(DIRTY|ALL)) or@org.hibernate.annotations.Immutable. - Why it matters: without optimistic version checks, concurrent read/modify/write operations can overwrite changes. Pessimistic locking, isolation, append-only use and external coordination may already protect the workflow; their absence is not inferred from annotations.
- Recommendation: add a numeric
@Versionattribute (for example aLong) so concurrent updates fail fast with an optimistic-lock exception; skip this only for append-only, read-only, or reference data where lost updates cannot occur. Hibernate documents timestamp versions as less reliable (see HIB-ENTITY-010).
HIB-ENTITY-009 - Unique business-key columns should consider @NaturalId
- Severity: INFO
- Inspects:
@Column(unique = true)attributes and entity-level@Table(uniqueConstraints = @UniqueConstraint(...))declarations. - Fires when: a unique column exists and no attribute on the entity is annotated
org.hibernate.annotations.NaturalId. - Why it matters: a unique business key (email, ISBN, order number) is frequently queried by value.
@NaturalIdlets Hibernate resolve the entity from the natural-id cache/lookup without needing a full JPQL query - a well-documented performance pattern. This is advisory, not a defect: a unique column is not always a natural lookup key, so the finding is INFO severity. - Recommendation: consider annotating the business-key attribute(s) with
@NaturalIdso lookups by that value can use Hibernate's natural-id resolution instead of a full query. Adding the annotation does not automatically reroute existing repository queries through natural-id lookup or cache APIs. - Quarkus/Panache: applies identically - this check inspects only annotation metadata via reflection.
HIB-ENTITY-010 - Prefer a numeric @Version over a timestamp
- Severity: INFO
- Inspects:
@Versionattributes and their Java type on entities that are not@Immutable. - Fires when: the version is a date or time type (
java.util.Date,Calendar, or ajava.timetemporal such asInstantorLocalDateTime). - Why it matters: Hibernate documents timestamps as a less reliable optimistic-locking mechanism than version numbers. Two updates within the clock or column precision can produce the same value, and clocks can differ between nodes. A timestamp version is still far better than none, and it is supported.
- Recommendation: prefer an
IntegerorLongversion and keep a separate audit timestamp when the modification time is needed. Keep a timestamp version when its precision and clock source are deliberate.
Query
All HIB-QUERY rules inspect verified Spring Data JPA repository metadata. Quarkus/Panache does not expose equivalent runtime repository/query metadata with enough fidelity to evaluate these rules reliably, so they remain unavailable on Quarkus rather than guessing from Panache method names or generated bytecode. Mapping, identifier, fetching, configuration, and caching rules still run on Quarkus against the live JPA metamodel.
The JPQL checks deliberately use bounded, conservative lexical analysis rather than a full JPQL parser. Comments and quoted literals are not fetch paths. Queries with nested aliases, subqueries, multiple roots, a root inconsistent with the repository domain, or unresolved rewriting/projection/graph effects may be skipped rather than inferred incorrectly. Named and dynamic queries outside the observed metadata remain outside coverage.
HIB-QUERY-001 - @Modifying bulk queries should clear stale persistence context
- Severity: INFO
- Inspects: Spring Data JPA
@Modifyingannotations on repository methods. - Fires when: a supported declared bulk query's merged
@Modifyingannotation does not setclearAutomatically. - Why it matters: the persistence context can hold stale entities after a bulk update or delete, leading to hard-to-diagnose data inconsistencies.
flushAutomaticallyonly synchronizes pending changes before the bulk query; it does not evict the stale managed entities afterward. - Recommendation: set
@Modifying(clearAutomatically=true)(andflushAutomatically=truewhen pending changes must be applied first), or manage affected state explicitly in a suitably isolated persistence context. Automatic clear detaches managed state and can discard unflushed changes; it is not an unconditional safe fix.
HIB-QUERY-002 - Review streaming query resource lifetime
- Severity: INFO
- Inspects: Spring Data repository methods returning
java.util.stream.Stream. - Fires when: a verified query-backed repository method returns
Stream<>. - Why it matters: streaming methods keep the underlying JDBC cursor open and only behave correctly inside an open transaction with the caller closing the stream.
- Recommendation: consume and close the stream within the required transaction, normally using try-with-resources. Read-only transaction hints can help read-only workloads but are not a universal correctness requirement. The method signature does not prove that the caller has failed to do this.
HIB-QUERY-003 - Native Page queries should review count derivation
- Severity: INFO
- Inspects: Spring Data
@Query(nativeQuery=true),@NativeQuery, and supported composed annotations returningPage<>. - Fires when:
countQueryis missing. - Why it matters: Spring Data can derive a count query for some simple native SQL, but complex SQL can require an explicit count query or JSqlParser support.
- Recommendation: review the generated count query. Add
countQuery = "..."when Spring Data cannot derive a correct count, especially for complex native SQL.
HIB-QUERY-004 - Derived deleteBy methods load entities before deletion
- Severity: MEDIUM
- Inspects: Spring Data derived query methods named
deleteBy...orremoveBy.... - Fires when: supported query metadata establishes derivation rather than merely a delete/remove method name. Known annotated, custom/default or unresolved named-query implementations are not assumed derived.
- Why it matters: Spring Data implements derived deletes by selecting matching entities first and deleting them one by one, which is expensive on large result sets.
- Recommendation: for bulk removals prefer an explicit
@Modifying @Query("delete from ... where ...")with appropriate persistence-context handling; reserve deriveddeleteBymethods for small or cascading deletes. Bulk DML changes callbacks, cascades and optimistic-lock behavior, so it is not a drop-in replacement.
HIB-QUERY-005 - Eager to-one associations should be JOIN FETCHed in entity-returning queries
- Severity: INFO
- Inspects: Spring Data JPQL
@Querymethods that return multiple whole entities (List/Set/Collection,Stream,Page,Slice, or arrays) on a repository whose domain entity declares an eager@ManyToOne/@OneToOneassociation (explicitFetchType.EAGERor the to-one default). Mapping-level@Fetch(JOIN)is not a JPQL exemption. - Fires when: such a query selects the whole root entity (for example
select o from Entity o) but does notJOIN FETCHthe eager to-one association. - Why it matters: JPQL can require secondary loads to satisfy eager mappings. Repeated targets, caches and actual fetch plans determine the query count; this does not prove a SELECT for every row.
- Recommendation:
JOIN FETCHthe eager association in the query, or map itFetchType.LAZY(see HIB-FETCH-001) and fetch it explicitly only where the use case needs it. Complements HIB-FETCH-001 by pinpointing the specific finders affected.
HIB-QUERY-006 - Paged or streamed reads should prefer DTO projections over whole entities
- Severity: INFO
- Inspects: Spring Data JPQL
@Querymethods that are paged or streamed (aPageableparameter, or aPage,Slice, orStreamreturn type). - Fires when: the query selects the whole root entity (for example
select o from Entity o) rather than a constructor expression (select new ...(...)) or an interface/DTO projection. - Why it matters: whole managed entities can load and track more state than a read model needs. Enhanced lazy basic attributes and read-only settings change that cost; whole-entity reads can be intentional.
- Recommendation: return a DTO/interface projection so Hibernate selects only the columns the caller needs; reserve whole-entity reads for cases that mutate the loaded entities.
HIB-QUERY-007 - Queries should not JOIN FETCH more than one collection
- Severity: MEDIUM
- Inspects: Spring Data repository
@Querymethods, resolvable JPQLJOIN FETCHpaths, and collection/bag metadata on the repository domain entity. - Fires when: a non-native JPQL query
JOIN FETCHes two or more collection associations from the same root entity. Runtime bag classification is not established, so presumed bags do not receive a higher-severity exception prediction. - Why it matters: simultaneous fetching of actual bags is unsupported, while parallel collections can multiply the row set. Java List declarations alone do not fully establish runtime BAG semantics, so the finding describes the parallel-fetch risk rather than predicting a guaranteed exception.
- Recommendation: fetch at most one collection per query. Initialize the remaining collections with separate queries, or suitably bounded batch fetching. An entity graph requesting the same parallel collections is not a universal fix, and Set does not eliminate row multiplication.
HIB-QUERY-008 - Bulk updates leave @Version unchanged
- Severity: MEDIUM
- Inspects: Spring Data repository
@Modifyingqueries targeting entities with a@Versionattribute. - Fires when: a JPQL/HQL bulk
UPDATEquery targets an entity that declares an optimistic-locking@Versionattribute (including via inheritance or property access) and neither usesUPDATE VERSIONEDnor a recognized explicit version assignment in itsSETclause. Recognized forms are numeric increments such ase.version = e.version + 1, parameterized increments such ase.version = e.version + :delta(both also accept reversed operands), direct named or numbered positional parameters such ase.version = :nextVersionore.lastModified = ?1, and timestamp assignments such ase.lastModified = CURRENT_TIMESTAMPore.lastModified = current_timestamp(). A version predicate only inWHERE, self-assignment, constant resets, and arbitrary expressions do not establish recognized maintenance. - Why it matters: bulk updates bypass entity state tracking and direct SQL updates leave
@Versionunchanged by default. Concurrent transactions holding previously loaded instances will not encounter optimistic locking failures on subsequent flushes, leading to lost updates.clearAutomatically=trueonly clears the calling persistence context and does not advance the database version. - Recommendation: review whether concurrent transactions require version invalidation. Where appropriate, use managed-entity updates, Hibernate's
update versionedsyntax, or explicitly maintain the version with a numeric increment, a type-compatible current timestamp, or a fresh version parameter (e.g.,set e.lastModified = :now). Recognition is a query-shape heuristic, not proof of runtime advancement: the check does not evaluate parameter values, validate expression type compatibility, or guarantee that timestamp precision yields a different value.
Configuration
HIB-CONFIG-001 - Open Session in View should be disabled
Retired. Open Session in View is a Spring MVC application setting, not a Hibernate factory option, and the Spring advisor's SPRING-JPA-001 reviews the same activation with richer registration evidence. Keeping both counted one observation against two advisor scores.
HIB-CONFIG-003 - Lazy loading outside transactions should stay disabled
- Severity: HIGH
- Inspects: the owning persistence unit's known lazy-loading-outside-transactions setting.
- Fires when: that observed setting is
true; unrelated or absent raw configuration does not establish runtime state. - Why it matters: it hides missing fetch plans by opening temporary sessions outside the intended transaction boundary.
- Recommendation: remove the setting and fetch required data inside transactions with explicit fetch plans or DTO queries.
HIB-CONFIG-004 - JDBC batching should be configured for writes
- Severity: INFO
- Inspects: the owning factory's effective JDBC batch size.
- Fires when: the observed size is less than 2. A batch size of 1 cannot combine multiple statements; an unreadable size is unknown, not disabled batching.
- Why it matters: write-heavy code otherwise sends insert/update/delete statements one at a time.
- Recommendation: benchmark a bounded batch size against representative writes. Read-mostly workloads may not benefit; no single range is optimal for every driver or statement shape.
- Quarkus: absence of
quarkus.hibernate-orm.jdbc.statement-batch-sizedoes not establish disabled batching. The observed Hibernate factory value is authoritative, including provider/dialect defaults.
HIB-CONFIG-005 - JDBC batching should order inserts and updates
- Severity: INFO
- Inspects: effective factory insert/update ordering when JDBC batching is enabled.
- Fires when: an observed batch size greater than 1 is configured but a known ordering option is disabled.
- Why it matters: batches are grouped by SQL/table shape; interleaved entity types reduce batch efficiency.
- Recommendation: benchmark ordering when writes are interleaved across multiple entity types; sorting adds cost.
- Quarkus:
hibernate.order_inserts/hibernate.order_updateshave no first-classquarkus.hibernate-orm.*equivalent, but Quarkus' genericquarkus.hibernate-orm.unsupported-properties."hibernate.order_inserts"(and"hibernate.order_updates") escape hatch reaches the effective factory options this rule reads. A live-boot spike againstbootui-quarkus-integration-testsconfirmed this end to end: withquarkus.hibernate-orm.jdbc.statement-batch-sizeand bothunsupported-propertiesentries set, Hibernate's ownSessionFactoryOptions.isOrderInsertsEnabled()/isOrderUpdatesEnabled()both reporttrueat runtime, and this rule correctly does not fire.
HIB-CONFIG-006 - Slow query logging should be available in development
- Severity: INFO
- Inspects: Hibernate slow-query threshold properties.
- Fires when: an observed threshold is not positive. Unavailable runtime evidence is not an unset effective default.
- Why it matters: a local slow-query threshold helps spot expensive SQL before it reaches shared environments.
- Recommendation: configure a bounded threshold in development and staging profiles.
- Quarkus:
quarkus.hibernate-orm.log.queries-slower-than-mssets the factory's effective threshold, so a native Quarkus threshold is recognized.
HIB-CONFIG-007 - Hibernate statistics should be enabled when tuning
- Severity: INFO
- Inspects: the live statistics-enabled state of the owning factory.
- Fires when: statistics are observed disabled.
- Why it matters: statistics expose query counts, fetch counts, and cache hit ratios useful during performance tuning.
- Recommendation: enable statistics in development or performance-test profiles when investigating data-access behavior. Leaving statistics disabled outside tuning sessions can intentionally avoid collection overhead.
- Quarkus:
quarkus.hibernate-orm.statisticsenables the same live factory statistics, so this rule does not report a unit whose statistics are enabled with the native Quarkus property name. - UI consumer: the standalone Hibernate Statistics panel (Database group) reads these same live counters once enabled — see the feature guide and the specification for the panel this recommendation unlocks.
HIB-CONFIG-008 - Connection providers should disable auto-commit explicitly
- Severity: INFO
- Inspects: attributed connection-provider and transaction evidence, not a stale Hikari property in isolation.
- Fires when: supported observations establish a resource-local provider guarantee that makes the recommendation applicable. Otherwise the check is skipped; BootUI never acquires a JDBC connection to discover auto-commit. Neither adapter supplies that observation today, so the check is always skipped with an
INFOdiagnostic. - Why it matters: when the pool already disables auto-commit, this setting lets Hibernate delay connection acquisition.
- Recommendation: configure the pool with auto-commit disabled and set
hibernate.connection.provider_disables_autocommit=true.
HIB-CONFIG-009 - Collection-parameter queries should use IN-clause padding
- Severity: INFO
- Inspects: Spring Data JPQL query methods with collection parameters and
INpredicates. - Fires when: such a query exists and
hibernate.query.in_clause_parameter_paddingis not enabled. - Why it matters: variable-length
INpredicates can produce many SQL shapes and reduce plan-cache reuse. - Recommendation: enable IN-clause parameter padding when the database benefits from statement plan reuse.
- Quarkus: the effective padding flag reflects
quarkus.hibernate-orm.query.in-clause-parameter-padding. The repository-scanning half of this rule only inspects Spring Data JPQL query methods, so it has nothing to flag on a Panache-based Quarkus app regardless of the property value.
HIB-CONFIG-010 - Query caching requires effective region support
- Severity: HIGH
- Inspects: effective factory query-cache state and region-factory availability.
- Fires when: query caching is enabled and effective region infrastructure is known unusable. Missing explicit region-factory configuration is not a failure, and disabled entity second-level caching does not itself prohibit query caching.
- Why it matters: query-result and timestamp regions require a working provider; entity regions have separate eligibility and concurrency semantics.
- Recommendation: configure appropriate provider infrastructure or disable query caching. Entity-cache coverage is a separate workload choice; factory query-cache enablement does not prove that any query opts in.
- Quarkus: Quarkus exposes a single unified
quarkus.hibernate-orm.second-level-caching-enabledtoggle for second-level/query caching, not a separate query-cache property; the rule reads the resulting effective factory query-cache state and region factory. When enabled, Quarkus supplies its integrated second-level cache implementation even though it does not expose ahibernate.cache.region.factory_classproperty.
HIB-CONFIG-011 - Review effective cache concurrency strategy
- Severity: MEDIUM
- Inspects: observed cache activation and available concurrency-strategy evidence.
- Fires when: supported evidence establishes a strategy problem, not merely absence of a Hibernate
@Cacheannotation. Providers may supply a valid default; unresolved effective strategy/eligibility is unknown. Neither adapter observes the provider-selected strategy today, so when a cache-enabled unit has@Cacheableentities without@Cachethe check is skipped with anINFOdiagnostic rather than reporting findings. - Why it matters: concurrency behavior must fit the data, but an explicit annotation is not mandatory.
- Recommendation: review the effective strategy and data mutability before adding annotations or changing cache use.
- Quarkus: the effective factory second-level-cache state reflects the same
quarkus.hibernate-orm.second-level-caching-enabledtoggle as HIB-CONFIG-010, so the precondition check for "second-level caching appears configured" is read correctly on Quarkus too.
HIB-CONFIG-002 - Schema generation should not mutate non-test databases
- Severity: INFO (can emit MEDIUM, HIGH, or CRITICAL based on profile and value)
- Inspects: the owning unit's normalized schema database action and active profiles.
- Fires when: a mutating action is observed outside a test profile. Production-like profiles take precedence: destructive drop/drop-and-create actions emit CRITICAL, create-only/update emit HIGH. Dev/local profiles emit INFO; unpinned non-test profiles emit MEDIUM. A profile name does not prove the database is disposable.
- Why it matters: automatic schema mutation is convenient locally but risky against shared or persistent databases, and destructive actions can remove live schema data. Hibernate
hbm2ddl.auto=createis destructive; Jakarta/Quarkuscreateis CREATE_ONLY and must not be described as guaranteed deletion. In Quarkus 3.33.3.1 an explicitly configured deprecateddatabase.generationtakes precedence overschema-management.strategy. - Recommendation: use versioned migrations for shared databases and reserve mutating
ddl-autovalues for disposable test environments.
HIB-CONFIG-012 - SQL logging should be off when a production profile is active
- Severity: MEDIUM
- Inspects:
spring.jpa.show-sql/hibernate.show_sql(statement output to standard out), the liveorg.hibernate.SQLlogger at DEBUG or finer, and the liveorg.hibernate.orm.jdbc.bindlogger at TRACE. - Fires when: any of those are enabled while a profile named
prod,production,staging,prod-*, or*-prod/*-productionis active. - Why it matters: statement logging adds workload-dependent overhead and can reveal sensitive SQL text. Parameter values have a separate binding logger checked by HIB-CONFIG-018; no measured throughput loss is inferred.
- Recommendation: keep SQL logging off in production-like environments and rely on structured slow-query logging or the database's statement audit.
HIB-CONFIG-013 - Review applicable JDBC temporal binding
- Severity: LOW
- Inspects: the owning unit's effective
hibernate.jdbc.time_zoneand the mapped temporal types. - Fires when: no JDBC time zone is configured and the unit maps a type that Hibernate binds through a JDBC
Calendar:LocalDateTime,LocalTime,OffsetTime,java.util.Dateand its timestamp/time subclasses, orCalendar.Instantbinds as UTC,LocalDateandjava.sql.Dateuse no calendar, and zoned or offset date-times follow their time-zone storage strategy, so those types alone do not make the rule applicable. - Why it matters: without a configured zone, those bindings use the JVM default time zone, so the stored wall-clock value can change when servers run in different zones. Missing
hibernate.jdbc.time_zoneis not wrong in itself; driver and converter semantics remain unobserved. - Recommendation: choose temporal types and binding semantics deliberately; configure a JDBC zone where needed, rather than treating UTC as a universal fix for every date/time mapping.
- Quarkus:
hibernate.jdbc.time_zonemaps toquarkus.hibernate-orm.jdbc.timezone, and the effective factory value is read directly.
HIB-CONFIG-014 - Hibernate's built-in connection pool should not be used
- Severity: HIGH
- Inspects: the actual selected Hibernate connection-provider classification.
- Fires when: the built-in test-oriented connection pool is observed selected.
pool_sizepresence does not select it in preference to a managed DataSource or explicit provider; unknown provider evidence is skipped. - Why it matters: the built-in pool is not intended for production use.
- Recommendation: use the framework's managed DataSource/provider or an appropriate production pool.
HIB-CONFIG-015 - Deferred script initialization should have an intentional order
- Severity: INFO
- Inspects:
spring.jpa.defer-datasource-initializationand whether Spring Boot registered aDataSourceScriptDatabaseInitializer. - Fires when: supported Spring initializer evidence establishes deferred initialization. Ignored Spring property names in Quarkus cannot activate this rule.
- Why it matters: the property moves script-based datasource initialization until after JPA initialization. That can be intentional with schema validation or externally managed schemas, but the initialization owner and ordering should be explicit.
- Recommendation: verify that script-based initialization has the intended owner and order. Do not infer that scripts are ineffective solely because Hibernate validates or does not generate the schema.
HIB-CONFIG-016 - Fail on pagination over collection fetch
- Severity: INFO
- Inspects: the owning unit's effective
hibernate.query.fail_on_pagination_over_collection_fetchguard. - Fires when: the guard is disabled. The rule reports one configuration finding per unit and never repeats the repository queries that HIB-FETCH-003 owns, so a risky query is not penalized twice.
- Why it matters: when a limited query fetch-joins a collection and Hibernate cannot apply the limit in SQL, it loads every matching row and limits the result in memory with only a warning. The guard turns that into an exception. Hibernate 7.4 pushes the limit into SQL where the dialect supports it, but a dialect fallback still reaches the guard, and an explicit
org.hibernate.limitInMemory=truehint bypasses the guard entirely. - Recommendation: consider
hibernate.query.fail_on_pagination_over_collection_fetch=trueso these queries fail fast during development, and fix the queries HIB-FETCH-003 reports. - Quarkus: maps to
quarkus.hibernate-orm.query.fail-on-pagination-over-collection-fetch; the effective factory value is read directly.
HIB-CONFIG-017 - Disable SQL formatting in production
- Severity: LOW
- Inspects: the effective
hibernate.format_sqlsetting, SQL-logging state, and active Spring or Quarkus profiles. - Fires when: a production profile is active,
hibernate.format_sqlistrue, and statement logging is enabled. Binder-only logging does not establish that SQL statements are formatted. - Why it matters: Hibernate formats a statement only when it logs that statement. Formatting every verbose SQL log line adds avoidable CPU and allocation work in production.
- Recommendation: disable
hibernate.format_sqlwhen verbose SQL logging is enabled in production. - Quarkus:
quarkus.hibernate-orm.log.format-sqldefaults totrue, but it is inert unlessquarkus.hibernate-orm.log.sqlor an equivalent Hibernate SQL logger is enabled.
HIB-CONFIG-018 - Bind-parameter logging should be off in production
- Severity: HIGH
- Inspects: the live TRACE level of the
org.hibernate.orm.jdbc.bindlogger, plus the active Spring/Quarkus profile. The legacyorg.hibernate.type.descriptor.sql.BasicBinderlogger is not inspected. - Fires when: a production-like profile is active and bind-parameter logging is enabled at TRACE.
- Why it matters: at TRACE, Hibernate logs every bound parameter value - this can leak PII, credentials, or tokens passed as query parameters into application logs.
- Recommendation: keep bind-parameter logging off in production; only enable it temporarily, in a non-production environment, while diagnosing a specific issue.
- Quarkus: the Quarkus-native
quarkus.hibernate-orm.log.bind-parametersconvenience flag raises that same logger to TRACE, so it is detected too - Quarkus's own guide explicitly warns against enabling this in production.
HIB-CONFIG-019 - SQL comments should be enabled intentionally
- Severity: INFO
- Inspects:
hibernate.use_sql_comments. - Fires when: generated SQL comments are enabled.
- Why it matters: comments add statement text. Varying text can affect statement-cache keys, but stable comments need not fragment caches, and the advisor does not measure either traffic or cache behavior.
- Recommendation: keep comments only when their measured observability benefit outweighs the statement-cache cost.
HIB-CONFIG-020 - Oracle JDBC fetch size should exceed the driver default
- Severity: INFO
- Inspects: the owning unit's observed dialect family and effective fetch size. Raw JDBC URLs and credentials are not report evidence.
- Fires when: Oracle is identifiable and the fetch size is absent or no greater than Oracle JDBC's default of 10.
- Why it matters: iterating result sets larger than ten rows can require avoidable database roundtrips. PostgreSQL and MySQL have different driver behavior, so this check deliberately does not prescribe a global fetch size.
- Recommendation: for Oracle queries that commonly return more than ten rows, benchmark a bounded fetch size above 10; retain the default when result sets are consistently small.
- Quarkus: the effective factory fetch size reflects
quarkus.hibernate-orm.jdbc.statement-fetch-size.
Caching
HIB-CACHE-001 - Cached entity association coverage should be reviewed
- Severity: INFO
- Inspects: entities annotated with
@Cacheableor Hibernate@Cacheand the entities they associate with. - Fires when: a cache-enabled unit contains an observed cached-source declaration pointing to an apparently uncached target. A collection's
@Cachestores membership, not the target entity state, and does not exempt its target. - Why it matters: loading a cached aggregate can still query an uncached target, but caching target entities and caching collection roles have different semantics and costs. The right coverage depends on real hit rates, mutability, and access patterns.
- Recommendation: measure cache hit rates and access patterns before caching associated entities or collection roles. Leave mutable or low-hit targets uncached when that better fits the workload.
- Limit: annotations are not complete provider eligibility evidence; factory enablement does not prove an entity region exists or gets useful hits.
HIB-CACHE-002 - READ_ONLY cache strategy on writable entities is unsafe
- Severity: MEDIUM
- Inspects: entities annotated with
@Cache(usage = READ_ONLY). - Fires when: caching is active and a non-immutable entity has an observed update-oriented declaration.
@Immutablewith@Versionalone is not writable, and disabled caches do not produce active-cache findings. - Why it matters: READ_ONLY is unsuitable for managed state that is actually updated. No cache strategy is inferred to observe external writers automatically.
- Recommendation: switch to
READ_WRITEorNONSTRICT_READ_WRITEfor mutable entities.
HIB-CACHE-003 - Immutable cached entities should use READ_ONLY
- Severity: INFO
- Inspects: entity-level Hibernate
@Immutableand@Cache(usage=...). - Fires when: an immutable cached entity uses
READ_WRITE,NONSTRICT_READ_WRITE, orTRANSACTIONAL. - Why it matters: Hibernate documents
READ_ONLYas the simplest, safest, and best-performing strategy for immutable entities; mutable strategies add coordination overhead for updates the mapping forbids. - Recommendation: consider
@Cache(usage = READ_ONLY)for genuinely immutable cached state; do not remove@Immutablemerely to silence a cache-efficiency prompt. Disabled caching makes this check inapplicable.
Primary research references
The audit covered every registered rule, including retained workload-dependent advice and the retired identifiers. These are primary implementation/specification sources, not proof of a measured problem in the host application.
| Topic | Sources and version caveats |
|---|---|
| Exact managed dependencies | Spring Boot 4.1.1 BOM, Quarkus 3.33.3.1 BOM. Application overrides remain possible. |
| Factory options and batching | ORM 7.2.19 options, ORM 7.4.5 options, batching guide. Factory defaults differ from session overrides. |
| Fetching and entity graphs | ORM 7.4.5 fetching, Persistence 3.2 entity operations. EAGER and graph requirements do not prove one SQL join. |
| Collection-fetch pagination | ORM 7.4.5 hint contract, execution path. Explicit in-memory hints and fallback prevent a blanket 7.4 safety claim. |
| Identifiers and records | Persistence 3.2 IdClass, EmbeddedId, Hibernate identifier guide, ORM 7.2.19 UUID styles. Record support and Serializable guidance follow Persistence 3.2. |
| Mapping defaults and lists | Persistence 3.2 discriminator, OrderColumn, ORM 7.4.5 collection semantics. List declarations are not complete runtime collection classification. |
| Enum/converter semantics | Persistence 3.2 Enumerated, Convert, Converter. Auto-apply and XML resolution remain outside declaration-only checks. |
| Entity equality and locking | Hibernate entity guide, locking guide. General guidance is pinned to 7.2.6; no method-body or workload inference is claimed. |
| Spring Data queries and newness | Spring Data JPA query methods 4.1.0, primitive identifier handling 4.1.1, primitive version fallback 4.1.1. Custom save/newness and runtime query rewriting need separate evidence. |
| Cache infrastructure and defaults | ORM 7.4.5 cache option construction, provider strategy defaults, Quarkus ORM guide 3.33.0. Cache enablement, region support and actual use are distinct. |
| Pool selection and OSIV | ORM 7.4.5 provider selection, Boot 4.1.1 OSIV activation, Spring 7.0.9 MVC delegation. Property or configurer-bean presence alone is insufficient. |
| Quarkus schema actions | 3.33.3.1 provider precedence, 3.33.3.1 action configuration, ORM 7.2.19 Action. Jakarta create-only and Hibernate destructive create differ. |
| Audit 2026-10 (effective settings, new rules) | ORM 7.4.5 SessionFactoryOptions and SqlStatementLoggerInitiator (effective defaults, legacy slow-query key), PersistentSet and CollectionEntry (HIB-MAP-023), PostgreSQLDialect and PostgreSQL large objects (HIB-MAP-024), SqmSelectionQueryImpl (guard bypassed by limitInMemory), TimestampJdbcType (HIB-CONFIG-013), and the user guide on timestamp versions (HIB-ENTITY-010). Spring Framework 7 requires ORM 7.1+, which retires HIB-MAP-021. |
| Kotlin and Panache | Kotlin 2.3.20 JPA change, Kotlin 2.3.10 plugin, Kotlin 2.3.21 plugin, Panache guide 3.33.0. Compiler output and platform transformation, not classpath presence, establish capability. |