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.