apache/shardingsphere · error · InvalidStatementHandleException
Invalid statement handle: %d
Error message
Invalid statement handle: %d
What it means
InvalidStatementHandleException thrown by FirebirdExecuteStatementCommandExecutor when the statement handle in an EXECUTE statement packet is absent from the connection's ServerPreparedStatementRegistry. The proxy refuses to execute what was never prepared or was already freed on this session.
Source
Thrown at proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/execute/FirebirdExecuteStatementCommandExecutor.java:83
@RequiredArgsConstructor
public final class FirebirdExecuteStatementCommandExecutor implements CommandExecutor {
private final FirebirdExecuteStatementPacket packet;
private final ConnectionSession connectionSession;
private ProxyBackendHandler proxyBackendHandler;
@Getter
private ResponseType responseType;
@Override
public Collection<DatabasePacket> execute() throws SQLException {
connectionSession.beginPreparedStatementCache(FirebirdStatementResourceCleaner.createPreparedStatementCacheKey(packet.getStatementId()));
try {
FirebirdServerPreparedStatement preparedStatement = connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(packet.getStatementId());
if (null == preparedStatement) {
throw new InvalidStatementHandleException(packet.getStatementId());
}
validateTransactionHandle();
ResponseHeader responseHeader = executePreparedStatement(preparedStatement, packet.getParameterValues());
if (responseHeader instanceof QueryResponseHeader) {
responseType = ResponseType.QUERY;
FirebirdFetchStatementCache.getInstance().registerStatement(connectionSession.getConnectionId(), packet.getStatementId(), proxyBackendHandler);
connectionSession.getDatabaseConnectionManager().markResourceInUse(proxyBackendHandler);
} else {
responseType = ResponseType.UPDATE;
preparedStatement.setAffectedRows(((UpdateResponseHeader) responseHeader).getUpdateCount());
}
Collection<DatabasePacket> result = new LinkedList<>();
if (packet.isStoredProcedure() && proxyBackendHandler.next()) {
result.add(getSQLResponse());
}
result.add(new FirebirdGenericResponsePacket());
return result;
} finally {View on GitHub (pinned to e952770a21)
Solutions
- Always PREPARE on the same connection before EXECUTE and take the handle from the prepare response.
- After FREE STATEMENT or session-level cleanup, re-prepare instead of reusing old handles.
- Never share statement handles across connections from a pool.
- Check for races where one thread frees statements while another executes them.
Defensive patterns
Strategy: validation
Validate before calling
Integer handle = preparedHandles.get(sql);
if (handle == null) { handle = sendPrepare(sql).statementHandle; preparedHandles.put(sql, handle); }
sendExecuteStatement(handle, params); Try / catch
catch (SQLException e) {
if (e.getMessage().contains("Invalid statement handle")) { int h = sendPrepare(sql).statementHandle; sendExecuteStatement(h, params); } else throw e;
} Prevention
- Invalidate cached handles on FREE/reconnect.
- Execute only handles obtained from prepare on the same connection.
When it happens
Trigger: execute() begins a prepared-statement cache scope, then getPreparedStatement(packet.getStatementId()) returns null; happens when EXECUTE precedes PREPARE, follows FREE STATEMENT (DROP/UNPREPARE), or uses a handle from a different connection.
Common situations: client (or ORM layer) executes a statement id it cached across a reconnect; FREE sent by a cleanup thread races with a pending EXECUTE; raw-protocol client misreads the handle from the prepare response; connection pool handle leakage between sessions.
Related errors
- Invalid statement handle: %d
- Invalid batch handle: statement handle %d
- Invalid batch handle: statement handle %d
- Unknown statement info request type %d
- Batch already opened for statement handle %d
AI-assisted analysis of apache/shardingsphere@e952770a21 (2026-08-14).
Data as JSON: /api/errors/83f27e5b317045b7.
Report an issue: GitHub.