apache/beam · error · IllegalArgumentException

Forbidden IOException when reading from InputStream

Error message

Forbidden IOException when reading from InputStream

What it means

decodeFromSafeStream wraps the coder's decode call over an UnownedInputStream and treats any escaping IOException as a contract violation, rethrowing it as IllegalArgumentException with this message (CoderExceptions pass through). Decoding an in-memory stream should only fail with CoderException for malformed data, never with raw I/O errors.

Source

Thrown at sdks/java/core/src/main/java/org/apache/beam/sdk/util/CoderUtils.java:145

    if (stream.available() != 0) {
      throw new CoderException(
          stream.available() + " unexpected extra bytes after decoding " + result);
    }
    return result;
  }

  /**
   * Decodes a value from the given {@code stream}, which should be a stream that never throws
   * {@code IOException}, such as {@code ByteArrayInputStream} or {@link
   * ExposedByteArrayInputStream}.
   */
  private static <T> T decodeFromSafeStream(
      Coder<T> coder, InputStream stream, Coder.Context context) throws CoderException {
    try {
      return coder.decode(new UnownedInputStream(stream), context);
    } catch (IOException exn) {
      Throwables.propagateIfPossible(exn, CoderException.class);
      throw new IllegalArgumentException(
          "Forbidden IOException when reading from InputStream", exn);
    }
  }

  private static ByteArrayOutputStream getThreadLocalOutputStream() {
    SoftReference<ExposedByteArrayOutputStream> refStream = threadLocalOutputStream.get();
    ExposedByteArrayOutputStream stream = refStream == null ? null : refStream.get();
    if (stream == null) {
      stream = new ExposedByteArrayOutputStream();
      threadLocalOutputStream.set(new SoftReference<>(stream));
    }
    stream.reset();
    return stream;
  }

  /**
   * Clones the given value by encoding and then decoding it with the specified Coder.
   *

View on GitHub (pinned to 12126d8942)

Solutions

  1. Examine the wrapped cause to find the underlying IOException source
  2. Fix the custom Coder so decode throws CoderException (or EOF-related exceptions the coder API allows) instead of raw IOException for data problems
  3. Validate input bytes (e.g. Base64 correctness) before calling decode
  4. Ensure the InputStream passed in is fully readable and not closed/broken

Example fix

// before
public T decode(InputStream in, Context ctx) throws IOException {
  return mapper.readValue(new GZIPInputStream(in), T.class); // raw IOException
}
// after
public T decode(InputStream in, Context ctx) throws IOException, CoderException {
  try {
    return mapper.readValue(new GZIPInputStream(in), T.class);
  } catch (IOException e) {
    throw new CoderException("malformed encoded value", e);
  }
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  T v = CoderUtils.decodeFromByteArray(coder, bytes);
} catch (IllegalArgumentException e) {
  throw new CoderException("decode hit an I/O failure", e.getCause());
}

Prevention

When it happens

Trigger: A custom Coder's decode() throws IOException not derived from CoderException; decoding a Base64 input via decodeFromBase64 whose underlying source stream errors; wrapped stream in a failed/closed state.

Common situations: Custom coders that perform additional I/O in decode(); stream wrappers (GZIP etc.) that throw mid-read; corrupted input sources passed where a stable in-memory stream was expected.

Understand the failure class

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/a5d36b0255d39bef. Report an issue: GitHub.