How to Fix MySQL 8 Case Sensitivity Issues with Spring Boot JPA Entities
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 logExceptionsERROR: Table 'mydb.USER_ACCOUNT' doesn't existWait, 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:
@Table(name = "USER_ACCOUNT")@Entitypublic 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:
┌─────────────────────────────────────────────────────────────────┐│ 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:
@Table(name = "user_account") // Changed to lowercase@Entitypublic 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:
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
@Tableand@Columnannotations - 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:
spring: jpa: hibernate: naming: physical-strategy: com.myapp.config.LowercaseNamingStrategyOr if you prefer application.properties:
spring.jpa.hibernate.naming.physical-strategy=com.myapp.config.LowercaseNamingStrategyWhy This Solution Works
After applying the custom naming strategy, the query flow looks like this:
┌─────────────────────────────────────────────────────────────────┐│ 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:
- Dump your database schema first
- Standardize all table and column names to lowercase in your database
- Apply the LowercaseNamingStrategy to ensure consistency going forward
# Find tables with uppercase charactersmysql -e "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'mydb' AND TABLE_NAME REGEXP '[A-Z]';"
# Find columns with uppercase charactersmysql -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
- Test database upgrades thoroughly - especially major version bumps
- Understand naming strategies - they’re a powerful abstraction for database portability
- Centralize naming conventions - a single strategy class is better than scattered annotations
- 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:
- 👨💻 MySQL 8.0 Identifier Case Sensitivity
- 👨💻 Spring Boot Application Properties
- 👨💻 Hibernate ORM 6.0 Release
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments