alibaba/canal · error · IOException
Get null field:{}#io
Error message
Get null field:{}#io What it means
Thrown in DirectLogFetcher.open() after successfully reading the 'io' field off the unwrapped ConnectionImpl: the field exists but holds null. The connection's internal MysqlIO object is null, meaning the connection is not (or no longer) in an initialized/connected state.
Source
Thrown at dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/DirectLogFetcher.java:182
/**
* Connect MySQL master to fetch binlog.
*/
public void open(Connection conn, String fileName, long filePosition, final int serverId, boolean nonBlocking)
throws IOException {
try {
this.conn = conn;
Class<?> connClazz = Class.forName("com.mysql.jdbc.ConnectionImpl");
Object unwrapConn = unwrapConnection(conn, connClazz);
if (unwrapConn == null) {
throw new IOException("Unable to unwrap " + conn.getClass().getName()
+ " to com.mysql.jdbc.ConnectionImpl");
}
// Get underlying IO streams for network communications.
Object connIo = getDeclaredField(unwrapConn, connClazz, "io");
if (connIo == null) {
throw new IOException("Get null field:" + conn.getClass().getName() + "#io");
}
mysqlOutput = (OutputStream) getDeclaredField(connIo, connIo.getClass(), "mysqlOutput");
mysqlInput = (InputStream) getDeclaredField(connIo, connIo.getClass(), "mysqlInput");
if (filePosition == 0) filePosition = BIN_LOG_HEADER_SIZE;
sendBinlogDump(fileName, filePosition, serverId, nonBlocking);
position = 0;
} catch (IOException e) {
close(); /* Do cleanup */
logger.error("Error on COM_BINLOG_DUMP: file = " + fileName + ", position = " + filePosition);
throw e;
} catch (ClassNotFoundException e) {
close(); /* Do cleanup */
throw new IOException("Unable to load com.mysql.jdbc.ConnectionImpl", e);
}
}
/**
View on GitHub (pinned to 87be50e876)
Solutions
- Validate the connection is usable (Connection.isValid(timeout) or a SELECT 1) before passing it to open().
- Obtain a fresh connection from the pool/driver immediately before open() rather than reusing a cached/closed one.
- Check logs for an earlier close/disconnect on the same connection object.
- Ensure the connection is not auto-evicted by the pool while replication is active (increase idle/leak thresholds).
Example fix
// before
fetcher.open(conn, file, pos, serverId); // conn may be closed -> io == null
// after - verify liveness first
if (conn == null || conn.isClosed() || !conn.isValid(2)) {
throw new IOException("Refusing to open fetcher on a dead connection");
}
fetcher.open(conn, file, pos, serverId); Defensive patterns
Strategy: validation
Validate before calling
// Reject dead connections before the fetcher reflects on them
if (conn == null || conn.isClosed() || !conn.isValid(2)) {
throw new IOException("refusing to open fetcher on a non-live connection");
} Try / catch
try { fetcher.open(conn, file, pos, serverId, false); }
catch (IOException e) {
if (e.getMessage().contains("Get null field") && e.getMessage().contains("#io"))
logger.error("connection 'io' is null - connection was closed/invalid", e);
throw e;
} Prevention
- Call Connection.isValid() or a SELECT 1 before open().
- Pull a fresh connection immediately before opening the fetcher.
- Keep the connection alive for the duration of replication (raise pool idle/leak limits).
When it happens
Trigger: getDeclaredField(unwrapConn, ConnectionImpl.class, "io") returns null - the connection object is a closed, half-constructed, or already-disconnected instance whose IO channel has been torn down.
Common situations: The connection was closed (explicit close or pool eviction) before open(); a prior error left the connection in a broken state; the pool handed back a stub; the connection was created but never fully connected to the master.
Related errors
- Unable to unwrap {} to com.mysql.jdbc.ConnectionImpl
- No such method: '{}' @ {}
- No such field: '{}' @ {}
- Unable to load com.mysql.jdbc.ConnectionImpl
- Invoke method failed: '{}' @ {}
AI-assisted analysis of alibaba/canal@87be50e876 (2026-08-14).
Data as JSON: /api/errors/ace3823313ec395a.
Report an issue: GitHub.