Skip to content

Hibernate PhysicalNamingStrategy Explained: How to Control Database Identifier Names

Database code Photo: Database and server code

Problem: Your @Table Annotation Gets Changed

You defined @Table(name = "UserInfo") in your entity class, but the database table becomes user_info? Or you wanted snake_case naming but got camelCase instead?

The key to understanding this is Hibernate’s PhysicalNamingStrategy.

Core Concept: Two Naming Strategies

Hibernate has two layers of naming strategies. Many developers confuse them:

Naming Strategy Flow
┌─────────────────────────────────────────────────────────────────┐
│ Entity Class │
│ @Entity, @Table, @Column annotations │
└──────────────────────────┬──────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ ImplicitNamingStrategy │
│ Determines logical names when no explicit name given │
│ (e.g., entity class name → logical table name) │
└──────────────────────────┬──────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Explicit Annotation Priority │
│ @Table(name="xxx") overrides implicit strategy result │
└──────────────────────────┬──────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ PhysicalNamingStrategy │
│ Final physical name transformation (processes all names) │
│ (e.g., add prefix, case conversion) │
└──────────────────────────┬──────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Database │
│ Actual SQL execution │
└─────────────────────────────────────────────────────────────────┘

Key difference:

  • ImplicitNamingStrategy: Only applies when no explicit name is specified, determines “logical names”
  • PhysicalNamingStrategy: Applies final transformation to all names, including names specified in @Table(name="xxx")

PhysicalNamingStrategy Interface

PhysicalNamingStrategy.java
public interface PhysicalNamingStrategy {
// Convert catalog name
Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment context);
// Convert schema name
Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment context);
// Convert table name
Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context);
// Convert sequence name
Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment context);
// Convert column name
Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context);
}

Each method receives two parameters:

  • Identifier name: The incoming logical name (may come from implicit strategy or explicit annotation)
  • JdbcEnvironment context: Database environment context, can get database dialect info

Practical Example: Snake Case Naming Strategy

The most common scenario is converting Java camelCase to database snake_case.

SnakeCaseNamingStrategy.java
public class SnakeCaseNamingStrategy implements PhysicalNamingStrategy {
@Override
public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment context) {
return name; // catalog name usually stays as-is
}
@Override
public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment context) {
return name; // schema name usually stays as-is
}
@Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
if (name == null) return null;
return Identifier.toIdentifier(toSnakeCase(name.getText()));
}
@Override
public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment context) {
if (name == null) return null;
return Identifier.toIdentifier(toSnakeCase(name.getText()));
}
@Override
public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
if (name == null) return null;
return Identifier.toIdentifier(toSnakeCase(name.getText()));
}
private String toSnakeCase(String camelCase) {
if (camelCase == null || camelCase.isEmpty()) {
return camelCase;
}
// camelCase → snake_case
return camelCase.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
}
}

Conversion effect:

Snake case conversion examples
UserInfo → user_info
createdAt → created_at
userId → user_id
isActive → is_active

Spring Boot Configuration

Option 1: application.yml Configuration

application.yml
spring:
jpa:
hibernate:
naming:
physical-strategy: com.yourpackage.SnakeCaseNamingStrategy

Option 2: Spring Boot Auto-configuration

NamingStrategyConfig.java
@Configuration
public class NamingStrategyConfig {
@Bean
public PhysicalNamingStrategy physicalNamingStrategy() {
return new SnakeCaseNamingStrategy();
}
}

Advanced Example: Adding Table Prefix

Sometimes you need to add a unified prefix to all tables (e.g., by module):

ModulePrefixNamingStrategy.java
public class ModulePrefixNamingStrategy implements PhysicalNamingStrategy {
private static final String TABLE_PREFIX = "app_";
@Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
if (name == null) return null;
// Skip if already has prefix
if (name.getText().startsWith(TABLE_PREFIX)) {
return name;
}
return Identifier.toIdentifier(TABLE_PREFIX + name.getText());
}
@Override
public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
return name; // Don't process column names
}
// Other methods use default implementation...
}

Conversion effect:

Prefix conversion examples
User → app_user
Order → app_order

Common Issues

Issue 1: @Table annotation name gets changed

Symptom: @Table(name = "T_USER") generates table name t_user

Reason: PhysicalNamingStrategy processes all names, including explicitly specified ones

Solution: Skip explicitly specified names in your strategy

RespectExplicitNamingStrategy.java
@Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
if (name == null) return null;
// If name is all uppercase or has special markers, keep as-is
if (name.getText().equals(name.getText().toUpperCase())) {
return name;
}
return Identifier.toIdentifier(toSnakeCase(name.getText()));
}

Issue 2: MySQL 8.0 Case Sensitivity

MySQL 8.0 on Linux systems is case-sensitive by default, which can cause “table not found” errors.

Solution: Use lowercase naming consistently

LowerCaseNamingStrategy.java
public class LowerCaseNamingStrategy implements PhysicalNamingStrategy {
@Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
if (name == null) return null;
return Identifier.toIdentifier(name.getText().toLowerCase());
}
@Override
public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
if (name == null) return null;
return Identifier.toIdentifier(name.getText().toLowerCase());
}
// Other methods similar...
}

Working with ImplicitNamingStrategy

Understanding how both strategies work together is important:

EntityExample.java
@Entity
public class OrderItem {
@Id
private Long id;
private Date createTime; // No @Column annotation
}

Processing flow:

Naming strategy collaboration
1. ImplicitNamingStrategy
OrderItem → OrderItem (default: class name is table name)
createTime → createTime (default: property name is column name)
2. PhysicalNamingStrategy (assuming SnakeCaseNamingStrategy)
OrderItem → order_item
createTime → create_time
Final SQL: CREATE TABLE order_item (id BIGINT, create_time TIMESTAMP)

Spring Boot Default Strategy

Spring Boot 2.x/3.x default configuration:

Spring Boot default naming
spring.jpa.hibernate.naming.implicit-strategy=
org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
spring.jpa.hibernate.naming.physical-strategy=
org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy

SpringPhysicalNamingStrategy behavior:

  • Replaces dots with underscores
  • Converts camelCase to snake_case
  • All letters lowercase

So the problem at the beginning: @Table(name = "UserInfo") became user_info — that’s the default strategy’s effect.

Best Practice Recommendations

  1. Decide strategy early in the project: Later changes affect database migration scripts
  2. Document your strategy: Let team members understand naming rules
  3. Consider database compatibility: Different databases handle case and special characters differently
  4. Be careful overriding explicit annotations: @Table(name="xxx") should be the final answer
  5. Unit test validation: Verify naming through schema generation

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments