apache/beam · error · IllegalArgumentException

Forbidden IOException when writing to OutputStream

Error message

Forbidden IOException when writing to OutputStream

What it means

CoderUtils.encodeToSafeStream encodes a value with a Coder over an UnownedOutputStream and asserts that coding should never raise a raw IOException. If one escapes (and is not a CoderException), it is rethrown as IllegalArgumentException with this message. It signals a coder bug or an underlying stream failure that the safe-encode contract does not expect.

Source

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

        return stream.toByteArray();
      } finally {
        threadLocalOutputStreamInUse.set(false);
      }
    }
  }

  /**
   * Encodes {@code value} to the given {@code stream}, which should be a stream that never throws
   * {@code IOException}, such as {@code ByteArrayOutputStream} or {@link
   * ExposedByteArrayOutputStream}.
   */
  private static <T> void encodeToSafeStream(
      Coder<T> coder, T value, OutputStream stream, Coder.Context context) throws CoderException {
    try {
      coder.encode(value, new UnownedOutputStream(stream), context);
    } catch (IOException exn) {
      Throwables.propagateIfPossible(exn, CoderException.class);
      throw new IllegalArgumentException("Forbidden IOException when writing to OutputStream", exn);
    }
  }

  /** Decodes the given bytes using the specified Coder, and returns the resulting decoded value. */
  public static <T> T decodeFromByteArray(Coder<T> coder, byte[] encodedValue)
      throws CoderException {
    return decodeFromByteArray(coder, encodedValue, Coder.Context.OUTER);
  }

  public static <T> T decodeFromByteArray(
      Coder<T> coder, byte[] encodedValue, Coder.Context context) throws CoderException {
    try (ExposedByteArrayInputStream stream = new ExposedByteArrayInputStream(encodedValue)) {
      T result = decodeFromSafeStream(coder, stream, context);
      if (stream.available() != 0) {
        throw new CoderException(
            stream.available() + " unexpected extra bytes after decoding " + result);
      }
      return result;

View on GitHub (pinned to 12126d8942)

Solutions

  1. Inspect the cause chain (exn.getCause()) to find the real IOException source
  2. Fix the custom Coder so encode() only throws CoderException for value-level problems
  3. Ensure the target OutputStream is open and healthy before calling CoderUtils.encodeToByteArray
  4. Wrap known-fragile coder calls and surface the underlying IOException with context

Example fix

// before
public void encode(T value, OutputStream out, Context ctx) throws IOException {
  myMaybeClosedStream.write(...); // throws IOException
}
// after
public void encode(T value, OutputStream out, Context ctx) throws CoderException {
  try {
    out.write(...);
  } catch (IOException e) {
    throw new CoderException("encode failed for value: " + value, e);
  }
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  byte[] bytes = CoderUtils.encodeToByteArray(coder, value);
} catch (IllegalArgumentException e) {
  throw new IllegalStateException("Coder/stream contract violation", e.getCause());
}

Prevention

When it happens

Trigger: A custom Coder's encode() implementation throws IOException for reasons other than malformed values (e.g. writes to a wrapped stream that fails); a coder writing to a stream already closed or in an error state.

Common situations: Custom coder implementations propagating stream I/O errors; closed or broken underlying output streams; coder implementations violating the contract that encoding to an unowned stream must not fail with IOException.

Understand the failure class

Related errors


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