apache/shardingsphere · error · IllegalStateException

Failed to read java.sql.Blob stream

Error message

Failed to read java.sql.Blob stream

What it means

Thrown while serializing a java.sql.Blob parameter for the Firebird wire protocol: the proxy opens the Blob's binary stream to read all bytes, and the JDBC call (getBinaryStream or the stream read) raised SQLException. The original exception is chained. It almost always means the Blob is backed by a closed connection or an already-freed temporary LOB.

Source

Thrown at database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/statement/execute/protocol/FirebirdBlobBinaryProtocolValue.java:105

    public Object read(final FirebirdPacketPayload payload) {
        return payload.readInt8();
    }
    
    @Override
    public void write(final FirebirdPacketPayload payload, final Object value) {
        long blobId;
        
        if (null == value) {
            blobId = 0L;
        } else if (value instanceof Long) {
            blobId = (Long) value;
        } else if (value instanceof byte[]) {
            blobId = register(payload.getConnectionId(), (byte[]) value);
        } else if (value instanceof Blob) {
            try {
                blobId = register(payload.getConnectionId(), readAllBytes(((Blob) value).getBinaryStream()));
            } catch (final SQLException ex) {
                throw new IllegalStateException("Failed to read java.sql.Blob stream", ex);
            } catch (final IOException ex) {
                throw new IllegalStateException("Failed to read java.sql.Blob content", ex);
            }
        } else if (value instanceof Clob) {
            try {
                Clob clob = (Clob) value;
                int len = (int) Math.min(Integer.MAX_VALUE, clob.length());
                String str = clob.getSubString(1L, len);
                blobId = register(payload.getConnectionId(), str.getBytes(payload.getCharset()));
            } catch (final SQLException ex) {
                throw new IllegalStateException("Failed to read java.sql.Clob", ex);
            }
        } else {
            blobId = register(payload.getConnectionId(), value.toString().getBytes(payload.getCharset()));
        }
        
        payload.writeInt8(blobId);
    }

View on GitHub (pinned to e952770a21)

Solutions

  1. Check the chained SQLException cause — 'connection closed', 'lob already freed', or similar pinpoints the lifetime problem
  2. Create the Blob and execute the statement within the same open connection/transaction
  3. Pass byte[] instead of java.sql.Blob when the data is already materialized in memory
  4. Call connection.commit()/keep the transaction alive per your driver's LOB lifetime rules before using the Blob

Example fix

// before: Blob created on one connection, used after it was closed
try (Connection c1 = ds.getConnection()) {
    Blob blob = c1.createBlob();
    blob.setBytes(1, data);
    saveForLater(blob);            // c1 closes -> blob dead
}
try (Connection c2 = ds.getConnection()) {
    ps.setBlob(1, loadFromEarlier());  // throws
}

// after: create and use the Blob on the same live connection, or send bytes
try (Connection c = ds.getConnection()) {
    ps.setBytes(1, data);
    ps.executeUpdate();
}
Defensive patterns

Strategy: validation

Validate before calling

// validate the Blob is still usable before binding
private static boolean isBlobReadable(final Blob blob, final Connection conn) {
    try {
        return !conn.isClosed() && blob.length() >= 0;
    } catch (SQLException ex) {
        return false;
    }
}
// if (!isBlobReadable(blob, conn)) -> re-materialize bytes or fail early with a clear message

Try / catch

try {
    ps.setBlob(1, blob);
} catch (IllegalStateException ex) {
    Throwable cause = ex.getCause();
    if (cause instanceof SQLException) {
        // Lob lifetime issue: re-read source data and bind bytes instead
        ps.setBytes(1, reloadFromSource());
    } else {
        throw ex;
    }
}

Prevention

When it happens

Trigger: Setting a java.sql.Blob parameter on a Firebird statement through the proxy where blob.getBinaryStream() or the subsequent stream read throws SQLException — typically because the connection/transaction that created the Blob was closed or rolled back before execution.

Common situations: Reusing a Blob obtained in a previous (now-committed or closed) connection; holding a Lob past free()/close(); driver-level LOB lifetime restrictions (free_temporary on commit).

Related errors


AI-assisted analysis of apache/shardingsphere@e952770a21 (2026-08-14). Data as JSON: /api/errors/780928214f0b2b66. Report an issue: GitHub.