tursodatabase/turso · error · SQLException
Failed to convert ${sql} into bytes
Error message
Failed to convert ${sql} into bytes What it means
SQLException thrown in TursoConnection.prepare when stringToUtf8ByteArray(sql) returns null — which happens only when the SQL string itself is null. The statement text must be UTF-8 encoded bytes before being passed to the native prepareUtf8, so a null statement cannot be compiled and is rejected with this message (the runtime text interpolates the null sql).
Source
Thrown at bindings/java/src/main/java/tech/turso/core/TursoConnection.java:135
/**
* Compiles an SQL statement with optional transaction start check.
*
* @param sql An SQL statement.
* @param checkTransaction Whether to check and start transaction if needed.
* @return Pointer to statement.
* @throws SQLException if a database access error occurs.
*/
private TursoStatement prepare(String sql, boolean checkTransaction) throws SQLException {
logger.trace("DriverManager [{}] [SQLite EXEC] {}", Thread.currentThread().getName(), sql);
// Ensure transaction is started if needed (lazy transaction start)
if (checkTransaction) {
ensureTransactionStarted(sql);
}
byte[] sqlBytes = stringToUtf8ByteArray(sql);
if (sqlBytes == null) {
throw new SQLException("Failed to convert " + sql + " into bytes");
}
return new TursoStatement(sql, prepareUtf8(connectionPtr, sqlBytes));
}
private native long prepareUtf8(long connectionPtr, byte[] sqlUtf8) throws SQLException;
// TODO: check whether this is still valid for turso
/**
* Checks whether the type, concurrency, and holdability settings for a {@link ResultSet} are
* supported by the SQLite interface. Supported settings are:
*
* <ul>
* <li>type: {@link ResultSet#TYPE_FORWARD_ONLY}
* <li>concurrency: {@link ResultSet#CONCUR_READ_ONLY})
* <li>holdability: {@link ResultSet#CLOSE_CURSORS_AT_COMMIT}
* </ul>
*
* @param resultSetType the type setting.View on GitHub (pinned to bad083fafb)
Solutions
- Null-check the SQL before preparing and fail fast with a meaningful application error or skip the statement
- Trace the null to its source — usually a missing config value or an absent mapping
- Make SQL constants non-null final so absence is caught at startup, not at query time
Example fix
// before
String sql = config.get("query.delete"); // null when the key is missing
try (TursoStatement st = conn.prepare(sql)) { st.execute(); } // Failed to convert null into bytes
// after
String sql = Objects.requireNonNull(config.get("query.delete"), "missing query.delete config");
try (TursoStatement st = conn.prepare(sql)) { st.execute(); } Defensive patterns
Strategy: validation
Validate before calling
if (sql == null || sql.isBlank()) {
throw new SQLException("SQL must be a non-empty string");
}
try (TursoStatement st = conn.prepare(sql)) {
st.execute();
} Type guard
static boolean isPreparable(String sql) {
return sql != null && !sql.isBlank();
} Prevention
- Make SQL constants non-null final and fail fast at config load when one is missing
- Reject null at your API boundary with a clear application error instead of letting it reach prepare
- Lint for config.get(...) values used directly as SQL without null checks
When it happens
Trigger: conn.prepare(null) or createStatement-level paths passing a null SQL string; a String variable that is null because a lookup, config key, or optional mapping produced nothing; an ORM/query-builder path that forwards null for a skipped query.
Common situations: Optional SQL sourced from configuration that was never set; refactors that moved SQL constants and left a null default; NPE-avoidance patterns that substitute null and push the failure to prepare time.
Related errors
- database connection closed
- SQLite only supports TYPE_FORWARD_ONLY cursors
- SQLite only supports CONCUR_READ_ONLY cursors
- SQLite only supports closing cursors at commit
- Cannot commit in autocommit mode.
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/3078e5656e53dbca.
Report an issue: GitHub.