Spring JDBC Automatic Type Conversion Bug: 11-digit Numeric Strings Converted to Long
Bug Description
Spring JDBC automatically converts 11-digit numeric strings to Long
type during parameter binding, while 10-digit numeric strings remain as String
. This behavior violates developer intent and causes SQL type mismatch errors with PostgreSQL.
Environment
- Spring Framework Version: [Your version]
- Database: PostgreSQL
- JDBC Driver: PostgreSQL JDBC Driver
- Java Version: [Your Java version]
Expected Behavior
When a String
parameter is passed to JdbcTemplate.query()
, it should always be bound as a VARCHAR/TEXT
type using PreparedStatement.setString()
, regardless of the string's numeric content.
Actual Behavior
- 10-digit numeric strings: Correctly bound as
String
→ works fine - 11-digit numeric strings: Automatically converted to
Long
→ causes PostgreSQL error
Error Message
PreparedStatementCallback; bad SQL grammar [...];
nested exception is org.postgresql.util.PSQLException:
ERROR: operator does not exist: character varying = bigint
Hint: No operator matches the given name and argument types.
You might need to add explicit type casts.
Code to Reproduce
Database Schema
CREATE TABLE a_firma (
firma_id BIGINT,
tckn_vkn CHARACTER VARYING -- This is a VARCHAR field
);
Java Code
public EdefterPaket edefterPaketByYearMonth(String vergiNo, Long yil, String ay) {
// vergiNo is definitely a String
System.err.println("vergiNo: " + vergiNo + " (" + vergiNo.getClass().getName() + ")");
return getJdbcTemplate().query(
"SELECT ep.firma_id, ep.donem_id, ep.paket_id FROM a_edefter_paket ep " +
"INNER JOIN a_firma f ON (f.firma_id = ep.firma_id AND f.durum_id <> 3) " +
"WHERE f.tckn_vkn = ? AND ep.yil = ? AND ep.ay = ? " +
"AND ep.durum_id = 1 AND ep.edefter_paket_durum = 3",
new ResultSetExtractor<EdefterPaket>() {
@Override
public EdefterPaket extractData(ResultSet rs) throws SQLException {
// implementation...
}
},
vergiNo, yil, ay // vergiNo is String but gets converted to Long!
);
}
Test Cases
// This works fine
edefterPaketByYearMonth("1234567890", 2024L, "01"); // 10 digits
// This fails with the error above
edefterPaketByYearMonth("33170598084", 2024L, "01"); // 11 digits
Current Workaround
The issue can be avoided by forcing a new String instance:
}, String.valueOf(vergiNo), yil, ay); // This works!
Root Cause Analysis
Spring JDBC's parameter binding logic appears to have a magic number threshold: - Numeric strings ≤ 10 digits: Treated as String - Numeric strings > 10 digits: Automatically converted to Long/BigInt
This behavior is: 1. Undocumented - No mention in Spring documentation 2. Inconsistent - Different behavior based on string length 3. Intent-violating - Developer explicitly passes String but framework converts it 4. Database-specific - Causes issues with PostgreSQL's strict type checking
Expected Fix
Spring JDBC should respect the developer's explicit type choice. If a String
is passed as a parameter, it should always be bound using PreparedStatement.setString()
, regardless of the string's content.
Impact
This bug affects any application using: - Spring JDBC with PostgreSQL - String parameters containing large numeric values (e.g., Tax IDs, Account Numbers) - VARCHAR/TEXT database columns that store numeric-looking data
Additional Notes
The String.valueOf()
workaround suggests that Spring's type inference is based on object identity or metadata, not just content. This indicates the issue is in Spring's parameter binding algorithm, not the PostgreSQL driver.
Priority: Medium-High (causes runtime failures in production) Category: Data Access / JDBC Labels: bug, jdbc, postgresql, type-conversion
Comment From: jhoeller
There is no magic String-to-int conversion in Spring's JDBC parameter binding, so it's unclear what this could be caused by. Please have a look at https://github.com/spring-projects/spring-framework/blob/da13a246040602249db6e509114173f326e160ae/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java#L328 ff, that's where the actual PreparedStatement.setXxx
delegation happens.
FWIW String.valueOf
will return the given String as-is, so there is no difference in terms of object identity. Without a SQL type specified, this should end up in https://github.com/spring-projects/spring-framework/blob/da13a246040602249db6e509114173f326e160ae/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java#L438 calling PreparedStatement.setString
. There must be something else involved in your scenario if this breaks, maybe stepping through it with the debugger can reveal it.