apache/beam · error · UnsupportedOperationException

GcsSeekableByteChannels are read-only and cannot be truncate

Error message

GcsSeekableByteChannels are read-only and cannot be truncated.

What it means

GcsUtilV2's GcsSeekableByteChannel is a read-only channel over a GCS object; truncate(long) unconditionally throws UnsupportedOperationException, as does write(). The channel API contract includes truncate for read-write channels, so the read-only implementation must reject it.

Source

Thrown at sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilV2.java:539

      checkArgument(newPosition >= 0, "Position must be non-negative: %s", newPosition);
      reader.seek(newPosition);
      this.position = newPosition;
      return this;
    }

    @Override
    public long position() throws IOException {
      return this.position;
    }

    @Override
    public long size() throws IOException {
      return size;
    }

    @Override
    public SeekableByteChannel truncate(long size) throws IOException {
      throw new UnsupportedOperationException(
          "GcsSeekableByteChannels are read-only and cannot be truncated.");
    }

    @Override
    public int write(ByteBuffer src) throws IOException {
      throw new UnsupportedOperationException(
          "GcsSeekableByteChannel are read-only and does not support writing.");
    }

    @Override
    public boolean isOpen() {
      return reader.isOpen();
    }

    @Override
    public void close() throws IOException {
      if (isOpen()) {
        reader.close();

View on GitHub (pinned to 12126d8942)

Solutions

  1. Don't truncate GCS read channels; to 'shrink' an object, copy the desired byte range to a new object and delete/replace the original.
  2. Check channel writability before truncate: use instanceof/capability checks or try-catch UnsupportedOperationException.
  3. For local filesystem operations, use a local channel, not the GCS one.
  4. If writable GCS channels are needed, use the write path (WriteChannel via create/update) rather than SeekableByteChannel.truncate.

Example fix

// before
channel.truncate(newSize); // UnsupportedOperationException on GCS channel
// after
if (channel instanceof GcsUtilV2.GcsSeekableByteChannel) {
  throw new IllegalStateException("GCS channels are read-only; copy range to new object instead");
}
Defensive patterns

Strategy: type-guard

Type guard

static boolean isWritableChannel(SeekableByteChannel ch) {
  return !(ch instanceof GcsUtilV2.GcsSeekableByteChannel);
}

Try / catch

try {
  channel.truncate(size);
} catch (UnsupportedOperationException e) {
  // GCS read channel: perform range-copy to a new object instead
}

Prevention

When it happens

Trigger: Calling SeekableByteChannel.truncate(size) on a channel obtained from gcsUtil.open (GCS read channel), directly or via file-manipulation utilities (e.g. Files.newByteChannel + truncate, or copy-with-replace helpers that assume writable channels).

Common situations: Passing a GCS-backed channel to generic java.nio file code that assumes local-file semantics; attempting in-place size adjustments of remote objects; test code reusing channel helpers written for local filesystems.

Related errors


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