tursodatabase/turso · error · SQLException
Unsupported object type in bindObject: " + x.getClass().getN
Error message
Unsupported object type in bindObject: " + x.getClass().getName()
What it means
TursoStatement.bindObject dispatches on the runtime type of the argument and only accepts null, Integer, Long, String, Float, Double, and byte[]. Any other non-null type falls into the else branch and throws this SQLException naming the offending class. There is no implicit conversion for Boolean, Short, Byte, BigDecimal, java.util.Date, java.sql.Timestamp, java.time types, UUID, or enums.
Source
Thrown at bindings/java/src/main/java/tech/turso/core/TursoStatement.java:240
}
if (x instanceof Byte) {
this.bindInt(parameterIndex, (Byte) x);
} else if (x instanceof Short) {
this.bindInt(parameterIndex, (Short) x);
} else if (x instanceof Integer) {
this.bindInt(parameterIndex, (Integer) x);
} else if (x instanceof Long) {
this.bindLong(parameterIndex, (Long) x);
} else if (x instanceof String) {
bindText(parameterIndex, (String) x);
} else if (x instanceof Float) {
bindDouble(parameterIndex, (Float) x);
} else if (x instanceof Double) {
bindDouble(parameterIndex, (Double) x);
} else if (x instanceof byte[]) {
bindBlob(parameterIndex, (byte[]) x);
} else {
throw new SQLException("Unsupported object type in bindObject: " + x.getClass().getName());
}
}
/**
* Returns total number of changes.
*
* @throws SQLException If a database access error occurs
*/
public long totalChanges() throws SQLException {
final long result = totalChanges(statementPointer);
if (result == -1) {
throw new SQLException("Exception while retrieving total number of changes");
}
return result;
}
private native long totalChanges(long statementPointer) throws SQLException;View on GitHub (pinned to bad083fafb)
Solutions
- Convert the value to a supported type before binding: boolean -> int 0/1, Short/Byte -> Integer, BigDecimal -> longValue()/doubleValue(), Date/Timestamp/java.time -> ISO-8601 String, UUID -> String, enum -> name().
- Prefer the typed setters (setInt, setLong, setString, setDouble, setBytes) instead of setObject when the type is known.
- Centralize the mapping in one helper (toSupportedSqlValue) so unsupported types fail with a clear message at the boundary.
- Add a type guard that rejects unsupported classes before they reach bindObject.
Example fix
// before
ps.setObject(1, Boolean.TRUE); // throws: java.lang.Boolean
ps.setObject(2, OffsetDateTime.now()); // throws: java.time.OffsetDateTime
ps.setObject(3, BigDecimal.valueOf("9.99")); // throws: java.math.BigDecimal
// after
ps.setInt(1, active ? 1 : 0);
ps.setString(2, OffsetDateTime.now().toString());
ps.setDouble(3, price.doubleValue()); Defensive patterns
Strategy: type-guard
Validate before calling
private static Object toSupportedSqlValue(Object x) {
if (x == null) return null;
if (x instanceof Boolean b) return b ? 1 : 0;
if (x instanceof Short || x instanceof Byte) return ((Number) x).intValue();
if (x instanceof BigDecimal bd) return bd.scale() <= 0 ? bd.longValue() : (Object) bd.doubleValue();
if (x instanceof java.util.Date || x instanceof java.time.temporal.Temporal)
return x instanceof java.sql.Timestamp t ? t : x.toString();
if (x instanceof UUID) return x.toString();
if (x instanceof Enum<?> e) return e.name();
return x;
} Type guard
private static boolean isSupportedBindValue(Object x) {
return x == null
|| x instanceof Integer || x instanceof Long
|| x instanceof String || x instanceof Float
|| x instanceof Double || x instanceof byte[];
} Try / catch
try {
stmt.bindObject(parameterIndex, x);
} catch (SQLException e) {
throw new IllegalArgumentException(
"unsupported bind value of type " + (x == null ? "null" : x.getClass().getName()), e);
} Prevention
- Route all setObject calls through one converter that whitelists supported types.
- Prefer typed setters (setInt/setLong/setString/setDouble/setBytes) when the type is known.
- Encode booleans as 0/1 and timestamps as ISO-8601 strings at the repository boundary.
- Fail fast in unit tests by asserting isSupportedBindValue for every value you plan to bind.
When it happens
Trigger: ps.setObject(i, Boolean.TRUE); ps.setObject(i, new Timestamp(...)); ps.setObject(i, BigDecimal.TEN); ps.setObject(i, someEnum); ps.setObject(i, UUID.randomUUID()); any generic DAO that forwards arbitrary objects to setObject.
Common situations: Generic ORM-lite layers and JDBC helpers that always call setObject; porting code from drivers with wider setObject type maps (PostgreSQL/MySQL accept many more types); domain objects with enums or money types bound directly.
Related errors
- database connection closed
- Failed to convert ${sql} into bytes
- SQLite only supports TYPE_FORWARD_ONLY cursors
- SQLite only supports CONCUR_READ_ONLY cursors
- SQLite only supports closing cursors at commit
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/204c31c8f3ebf8f5.
Report an issue: GitHub.