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

  1. Validate the connection is usable (Connection.isValid(timeout) or a SELECT 1) before passing it to open().
  2. Obtain a fresh connection from the pool/driver immediately before open() rather than reusing a cached/closed one.
  3. Check logs for an earlier close/disconnect on the same connection object.
  4. 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

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


AI-assisted analysis of alibaba/canal@87be50e876 (2026-08-14). Data as JSON: /api/errors/ace3823313ec395a. Report an issue: GitHub.