A Hibernate entity field named createdAt is mapped to a column literally called createdAt, but the database convention is created_at. How do you make Hibernate map camelCase Java names to snake_case identifiers across the whole schema?
answer
- default = property name verbatim
- hibernate.physical_naming_strategy
- CamelCaseToUnderscoresNamingStrategy
- rewrites annotated names too
- schema migration if retrofitted
basics
~10 sSet hibernate.physical_naming_strategy to a snake-casing implementation - Hibernate ships CamelCaseToUnderscoresNamingStrategy. It rewrites every table, column and sequence name, including ones written explicitly in annotations, so you do not annotate each field.
solid answer
~40 sHibernate's default implicit strategy is JPA-compliant, which copies the property name verbatim, so createdAt becomes column createdAt. To get snake_case, install a PhysicalNamingStrategy: hibernate.physical_naming_strategy = org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy (or your own class). It inserts an underscore at each camel-case boundary and lower-cases the result, so createdAt becomes created_at and entity CustomerOrder becomes customer_order. I choose the physical hook rather than annotating every field because it applies to all identifiers uniformly and cannot be forgotten, and because it also normalises names that were written explicitly. The alternative - @Column(name = "created_at") on every attribute - is verbose and drifts. One caution: this is a boot-time setting affecting generated DDL and every emitted statement, so introducing it against an existing schema means renaming columns in a migration.
code
java · 6 lines@Entity
public class CustomerOrder {
@Id @GeneratedValue Long id;
Instant createdAt;
@ManyToOne Customer customer;
}go deeper
Name the property hibernate.physical_naming_strategy and the built-in camel-to-underscore implementation, and say why annotating every column is worse.
Explain that the physical pass also rewrites explicitly annotated names and what that means for opting out.
Raise the rollout cost against an existing schema and the interaction with database identifier case folding.
Treat identifier conventions as cross-service governance: one shared strategy artifact, versioned, so every service produces the same schema shape.
## Why the default is not snake_case Hibernate's out-of-the-box implicit strategy, ImplicitNamingStrategyJpaCompliantImpl, is deliberately JPA-spec faithful: the default column name is the property name and the default table name is the unqualified entity name, character for character. Java conventions are camelCase and most SQL schemas are snake_case, so the mismatch is universal - which is why practically every project installs a naming strategy. ## The right hook Two hooks exist. The implicit one only fires for names you did not state; the physical one is applied to every logical name, annotated or not. A case convention must hold for the whole schema, so it belongs in the PhysicalNamingStrategy. Hibernate 6.2 ships org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy for exactly this; on older versions you write a small class yourself. The transformation is mechanical: walk the identifier, and where a lower-case or digit character is followed by an upper-case one, insert an underscore, then lower-case everything. CustomerOrder becomes customer_order, createdAt becomes created_at, and a join column derived as customerId + underscore + id becomes customer_id. ## What it touches Because it is the physical pass, it rewrites table names, column names, sequence names and (depending on the implementation) schema and catalog names. It also rewrites names you wrote yourself: @Column(name = "emailAddress") comes out as email_address. That surprises people, and it is exactly the property that makes the convention airtight - but it means you cannot use an annotation to opt one column out of the rule unless your strategy honours quoting or you special-case it. It does not touch JPQL. Queries reference entity and attribute names from the Java model, so switching strategy never edits a query. It does change every SQL statement and all generated DDL. ## Alternatives and their costs Annotating each attribute with an explicit snake_case @Column works but scales badly: dozens of lines of ceremony, and the first person to add a field without the annotation breaks the convention silently. A custom ImplicitNamingStrategy would snake-case defaults but leave hand-written names alone, giving an inconsistent schema. So the physical hook is the standard answer. ## Rollout care This is a bootstrap setting. Turning it on for a schema that already exists in camelCase means either renaming every column in a migration or keeping the old strategy. On PostgreSQL there is a subtlety: unquoted identifiers are folded to lower case anyway, so createdAt in unquoted SQL already resolves to createdat - snake_case removes that ambiguity as well as the readability problem. On a case-sensitive setup the mismatch is a hard error rather than a silent fold, so verify against the real dialect before shipping.
- If you install that strategy, what happens to a field already annotated @Column(name = "emailAddress")?It is rewritten to email_address, because the physical strategy is applied to every logical name including explicitly annotated ones. If you truly need one column to keep its exact spelling you must handle it inside the strategy, for example by leaving quoted identifiers untouched.
- Why not just annotate each field with the snake_case name instead?It works but does not scale: every new attribute needs the annotation, and the first omission silently breaks the convention. A single boot-time physical strategy applies uniformly, is reviewable in one place, and keeps entity code free of naming ceremony.
saying these in an interview costs you the question
- Claiming Hibernate snake-cases names by default - the JPA-compliant default copies them verbatim
- Putting the case convention in an ImplicitNamingStrategy, so annotated names escape it
- Believing the change also rewrites JPQL attribute names
- Enabling it on an existing camelCase schema without a rename migration
- Confusing the setting with hibernate.globally_quoted_identifiers, which quotes rather than transforms