In Spring Data JDBC, how are table and column names resolved for an entity, and does the default convert camelCase to snake_case?
answer
- Default = names as-is
- No auto snake_case (that's Hibernate/JPA)
- @Table / @Column to override one
- NamingStrategy bean to override all
- DB lowercase-folding hides it
basics
~10 sNames come from the NamingStrategy. The default uses the class and property names as-is — no automatic snake_case. Property firstName maps to column firstName, not first_name, unless you override it.
solid answer
~40 sSpring Data JDBC resolves names through the NamingStrategy interface. Its default implementation returns the simple class name for tables and the property name for columns, verbatim — it does NOT convert camelCase to snake_case (that camel-to-underscore behavior is Hibernate/JPA's physical naming strategy, not Spring Data JDBC). So entity Customer with property firstName maps to table Customer, column firstName. Databases that fold unquoted identifiers to lowercase can hide this. To fix individual mappings use @Table("customers") on the class and @Column("first_name") on the field. To change the global convention, register a custom NamingStrategy bean that lower-snake-cases getTableName and getColumnName. This is a frequent surprise for people migrating from Spring Data JPA, where Spring Boot auto-applies CamelCaseToUnderscoresNamingStrategy.
code
java · 31 linesimport org.springframework.data.relational.core.mapping.NamingStrategy;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class JdbcNamingConfig {
// Make Spring Data JDBC behave like JPA: camelCase -> snake_case
@Bean
NamingStrategy namingStrategy() {
return new NamingStrategy() {
@Override
public String getColumnName(RelationalPersistentProperty property) {
return toSnake(property.getName());
}
@Override
public String getTableName(Class<?> type) {
return toSnake(type.getSimpleName());
}
private String toSnake(String s) {
return s.replaceAll("([a-z0-9])([A-Z])", "$1_$2").toLowerCase();
}
};
}
}
// Without the bean above, this maps to table "Customer", column "firstName".
// class Customer { @Id Long id; String firstName; }
// Per-element override instead of a global bean:
// @Table("customers") class Customer { @Column("first_name") String firstName; }go deeper
Know that names come from NamingStrategy and the default is verbatim; use @Table/@Column to fix mismatches.
Explain the JPA-vs-JDBC difference and how DB case-folding masks it.
Register a global NamingStrategy bean and reason about which methods (getColumnName, getTableName, getSchema) to override.
Weigh a global strategy vs explicit @Table/@Column for readability and refactor-safety across the model.
## What NamingStrategy is `org.springframework.data.relational.core.mapping.NamingStrategy` is the interface Spring Data JDBC (and Spring Data R2DBC) uses to translate Java identifiers into SQL identifiers. Whenever the framework builds SQL for an entity, it asks the active NamingStrategy for the table name, column names, key columns, and reverse (back-reference) columns. ## The default implementation The interface ships default methods that map names **verbatim**: - `getTableName(Class<?> type)` → `type.getSimpleName()` (e.g. class `Customer` → table `Customer`). - `getColumnName(RelationalPersistentProperty property)` → `property.getName()` (e.g. field `firstName` → column `firstName`). - `getSchema()` → `""` (no schema prefix). There is **no camelCase-to-snake_case conversion**. This is the single biggest gotcha. Many developers assume `firstName` becomes `first_name` because that is what **Hibernate/JPA** does — Spring Boot registers `CamelCaseToUnderscoresNamingStrategy` as Hibernate's physical naming strategy. Spring Data JDBC has **no** such default. ## Why it often "seems to work" anyway On PostgreSQL and most databases, **unquoted** identifiers are folded to lowercase. So `firstName` in generated SQL becomes `firstname`, and if your column is `firstname` it matches. But a column named `first_name` will **not** match — you get `column "firstname" does not exist`. ## How to control names 1. **Per element:** `@Table("customers")` on the class, `@Column("first_name")` on the property. These annotations override the NamingStrategy for that specific element. `@Table` also accepts a `schema`. 2. **Globally:** register a `NamingStrategy` bean. Overriding `getColumnName` and `getTableName` to lower-snake-case everything gives JPA-like behavior across the whole model. ## Edge cases / gotchas - Quoting: if identifiers contain uppercase and you want them preserved, the DB may require quoting; verbatim names plus case-folding is the usual source of confusion. - `AggregateReference` and `@Embedded` columns are named through the **same** NamingStrategy, so a global custom strategy affects them too (embedded prefix is applied on top). - Back-reference columns in `@MappedCollection` relationships come from `getReverseColumnName`, not `getColumnName`.
- A colleague swears Spring Data JDBC maps firstName to first_name automatically. Why might their app work despite the default not doing that?They likely rely on @Column overrides, a custom NamingStrategy bean, or their columns are actually named firstname and the DB folds the unquoted identifier to lowercase so it coincidentally matches.
- Where does the snake_case-by-default belief come from?From Spring Data JPA/Hibernate, where Spring Boot registers CamelCaseToUnderscoresNamingStrategy as the physical naming strategy. Spring Data JDBC does not share that default.
saying these in an interview costs you the question
- Claiming Spring Data JDBC converts camelCase to snake_case out of the box
- Saying @Column is required for every field (only needed to override the default)
- Confusing Hibernate's physical naming strategy with Spring Data JDBC's NamingStrategy