Tencent/tinker · error · NullPointerException

in == null

Error message

in == null

What it means

Streams.readFully(InputStream, byte[], int, int) is a strict replacement for DataInputStream.readFully: it validates its arguments before doing I/O. When byteCount > 0 and the supplied InputStream is null it throws NullPointerException("in == null") — a programming-error guard, not an I/O condition, signaling the caller passed an unopened/failed stream reference.

Source

Thrown at third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/Streams.java:75

    /**
     * Fills 'dst' with bytes from 'in', throwing EOFException if insufficient bytes are available.
     */
    public static void readFully(InputStream in, byte[] dst) throws IOException {
        readFully(in, dst, 0, dst.length);
    }

    /**
     * Reads exactly 'byteCount' bytes from 'in' (into 'dst' at offset 'offset'), and throws
     * EOFException if insufficient bytes are available.
     *
     * Used to implement {@link java.io.DataInputStream#readFully(byte[], int, int)}.
     */
    public static void readFully(InputStream in, byte[] dst, int offset, int byteCount) throws IOException {
        if (byteCount == 0) {
            return;
        }
        if (in == null) {
            throw new NullPointerException("in == null");
        }
        if (dst == null) {
            throw new NullPointerException("dst == null");
        }
        Arrays.checkOffsetAndCount(dst.length, offset, byteCount);
        while (byteCount > 0) {
            int bytesRead = in.read(dst, offset, byteCount);
            if (bytesRead < 0) {
                throw new EOFException();
            }
            offset += bytesRead;
            byteCount -= bytesRead;
        }
    }
    /**
     * Returns a byte[] containing the remainder of 'in', closing it when done.
     */
    public static byte[] readFully(InputStream in) throws IOException {

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Ensure the InputStream is opened (non-null) before constructing readers that consume it; propagate open failures instead of null.
  2. Change null-returning factory methods to throw the original IOException so the NPE never masks the root cause.
  3. In tests, pass a real ByteArrayInputStream or a proper mock rather than null.

Example fix

// before: factory returns null on failure, caller passes it on
InputStream open(String name) {
    try { return files.get(name); }
    catch (IOException e) { return null; }
}
Streams.readFully(open(name), buf, 0, len); // NPE: in == null

// after: propagate the failure
InputStream open(String name) throws IOException {
    return files.get(name);
}
try (InputStream in = open(name)) {
    Streams.readFully(in, buf, 0, len);
}
Defensive patterns

Strategy: validation

Validate before calling

// Ensure a non-null stream before parsing
java.util.Objects.requireNonNull(in, "input stream must be opened before reading");
Streams.readFully(in, dst, 0, len);

Prevention

When it happens

Trigger: Any internal ziputils read path (TinkerZipEntry parsing, TinkerZipFile streaming) invoking Streams.readFully with a null InputStream — typically because a stream-open call earlier returned null after a caught exception instead of propagating.

Common situations: Wrapped stream factories that return null on failure (anti-pattern) feeding zip readers; fields initialized lazily that were never set because of an earlier error; test harnesses constructing parsers with null stream stubs.

Related errors


AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14). Data as JSON: /api/errors/f02c23d6ca708c61. Report an issue: GitHub.