Skip to content

How to Fix MySQL 8 Case Sensitivity Issues with Spring Boot JPA Entities


Database code Photo: Database and server code

The Error That Stopped Everything

I deployed my Spring Boot application to production last week. It had been running smoothly on MySQL 5.7 for months. But after the infrastructure team upgraded our database to MySQL 8, everything broke:

org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
ERROR: Table 'mydb.USER_ACCOUNT' doesn't exist

Wait, what? The table definitely exists. I could see it in my database client:

mysql> SHOW TABLES;
+------------------+
| Tables_in_mydb |
+------------------+
| user_account |
+------------------+

The table is there, but Hibernate can’t find it. Why is MySQL looking for USER_ACCOUNT when the actual table is user_account?

The Root Cause: MySQL 8’s Case Sensitivity Change

After hours of debugging, I discovered the culprit: MySQL 8 changed how it handles case sensitivity.

In MySQL 5.7, you could set lower_case_table_names=1 in your configuration, which would make MySQL case-insensitive for table and column names. This meant USER_ACCOUNT, User_Account, and user_account were all treated the same.

MySQL 8 removed this capability. Now, identifiers are case-sensitive on Unix-based systems. The database expects exact matches.

How Spring Boot JPA Fits Into This Problem

My JPA entity looked like this:

UserAccount.java
@Table(name = "USER_ACCOUNT")
@Entity
public class UserAccount {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "USER_NAME")
private String userName;
@Column(name = "EMAIL_ADDRESS")
private String emailAddress;
// getters and setters
}

When Hibernate generates SQL queries, it uses the exact names from the @Table and @Column annotations. So it produced:

SELECT * FROM USER_ACCOUNT WHERE USER_NAME = 'john';

But my actual table structure in the database is:

CREATE TABLE user_account (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(255),
email_address VARCHAR(255)
);

MySQL 8 on Linux says: “I don’t know any table called USER_ACCOUNT. I only know user_account.”

The Architecture Flow

Here’s what happens when your application queries the database:

Query Flow Diagram
┌─────────────────────────────────────────────────────────────────┐
│ Spring Boot Application │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Entity Class Hibernate Naming Strategy │
│ ┌──────────────────┐ ┌─────────────────────────┐ │
│ │ @Table( │ │ PhysicalNamingStrategy │ │
│ │ name= │──────────────▶│ │ │
│ │ "USER_ACCOUNT" │ │ Converts logical names │ │
│ │ ) │ │ to physical DB names │ │
│ └──────────────────┘ └───────────┬─────────────┘ │
│ │ │
│ ▼ │
│ Generated SQL Query: │
│ SELECT * FROM USER_ACCOUNT │
│ │ │
└────────────────────────────────────────────────│─────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ MySQL 8 Database │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Actual Table: user_account (lowercase) │
│ Requested: USER_ACCOUNT (uppercase) │
│ │
│ ❌ Case mismatch on Unix systems! │
│ │
└─────────────────────────────────────────────────────────────────┘

My Failed Attempts

Attempt 1: Rename Everything in the Database

I thought, “Let me just rename the tables to uppercase to match my entities.”

RENAME TABLE user_account TO USER_ACCOUNT;

But this created a cascade of problems:

  • Other applications expected lowercase names
  • My database migration scripts were inconsistent
  • Development environment databases were lowercase

This approach was too risky and would break other systems.

Attempt 2: Change All Entity Annotations

I could change every @Table and @Column to lowercase:

UserAccount.java (Attempt 2)
@Table(name = "user_account") // Changed to lowercase
@Entity
public class UserAccount {
@Column(name = "user_name") // Changed to lowercase
private String userName;
@Column(name = "email_address") // Changed to lowercase
private String emailAddress;
}

But I had over 50 entities across multiple modules. This was tedious and error-prone. Plus, it felt wrong to litter my codebase with lowercase annotations when the database naming convention should be a configuration concern.

Attempt 3: Use Hibernate’s PhysicalNamingStrategy

Then I found the proper solution: Hibernate allows you to customize how it converts logical names (from annotations) to physical names (in the database).

The Solution: Custom PhysicalNamingStrategy

I created a custom naming strategy that converts all identifiers to lowercase:

LowercaseNamingStrategy.java
package com.myapp.config;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
public class LowercaseNamingStrategy implements PhysicalNamingStrategy {
@Override
public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment context) {
return convertToLowercase(name);
}
@Override
public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment context) {
return convertToLowercase(name);
}
@Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
return convertToLowercase(name);
}
@Override
public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment context) {
return convertToLowercase(name);
}
@Override
public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
return convertToLowercase(name);
}
private Identifier convertToLowercase(Identifier name) {
if (name == null) {
return null;
}
return Identifier.toIdentifier(name.getText().toLowerCase());
}
}

The key insight here is understanding the difference between logical naming and physical naming:

  • Logical naming: What you write in your @Table and @Column annotations
  • Physical naming: What actually gets used in the SQL statements sent to the database

The PhysicalNamingStrategy sits between your annotations and the generated SQL, allowing you to transform names consistently.

Configuring the Strategy in Spring Boot

I added this configuration to my application.yml:

application.yml
spring:
jpa:
hibernate:
naming:
physical-strategy: com.myapp.config.LowercaseNamingStrategy

Or if you prefer application.properties:

application.properties
spring.jpa.hibernate.naming.physical-strategy=com.myapp.config.LowercaseNamingStrategy

Why This Solution Works

After applying the custom naming strategy, the query flow looks like this:

Fixed Query Flow
┌─────────────────────────────────────────────────────────────────┐
│ Spring Boot Application │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Entity Annotation LowercaseNamingStrategy │
│ ┌──────────────────┐ ┌─────────────────────────┐ │
│ │ @Table( │ │ toPhysicalTableName() │ │
│ │ name= │──────────────▶│ │ │
│ │ "USER_ACCOUNT" │ │ "USER_ACCOUNT" → │ │
│ │ ) │ │ "user_account" │ │
│ └──────────────────┘ └───────────┬─────────────┘ │
│ │ │
│ ▼ │
│ Generated SQL Query: │
│ SELECT * FROM user_account │
│ (lowercase!) │
│ │ │
└────────────────────────────────────────────────────────│─────────┘
┌─────────────────────────────────────────────────────────────────┐
│ MySQL 8 Database │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Actual Table: user_account (lowercase) │
│ Requested: user_account (lowercase) │
│ │
│ ✅ Perfect match! Query succeeds. │
│ │
└─────────────────────────────────────────────────────────────────┘

Now Hibernate generates lowercase SQL regardless of what case I use in my entity annotations:

SELECT * FROM user_account WHERE user_name = 'john';

MySQL 8 is happy, and my application works again.

What About Existing Databases?

If you’re migrating from MySQL 5.7, your existing databases might have mixed case tables. Here’s what I recommend:

  1. Dump your database schema first
  2. Standardize all table and column names to lowercase in your database
  3. Apply the LowercaseNamingStrategy to ensure consistency going forward
Check for case mismatches
# Find tables with uppercase characters
mysql -e "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'mydb' AND TABLE_NAME REGEXP '[A-Z]';"
# Find columns with uppercase characters
mysql -e "SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'mydb' AND COLUMN_NAME REGEXP '[A-Z]';"

Alternative Approaches

There are other ways to handle this, though I don’t recommend them:

Using SnakeCaseStrategy

Hibernate provides a CamelCaseToSnakeCaseStrategy but it converts to snake_case, not lowercase. So UserAccount becomes user_account (good), but USER_ACCOUNT stays user_account anyway. It’s more opinionated than my solution.

Database-Level Configuration

Some database administrators try to solve this at the MySQL level, but MySQL 8 doesn’t support lower_case_table_names=1 after initialization. You’d need to recreate your entire database with this setting during MySQL initialization—not practical for existing systems.

Lessons Learned

  1. Test database upgrades thoroughly - especially major version bumps
  2. Understand naming strategies - they’re a powerful abstraction for database portability
  3. Centralize naming conventions - a single strategy class is better than scattered annotations
  4. Document your assumptions - our team assumed MySQL would always be case-insensitive

The MySQL 8 upgrade taught me that relying on database-specific behavior is fragile. A well-structured naming strategy makes your application more portable and resilient to infrastructure changes.

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