oracle/graal · error · UnsupportedOperationException

open option: {}

Error message

open option: {}

What it means

Thrown by TruffleFileSystemProvider.openOptionsToMask when an OpenOption in the set is not in SUPPORTED_OPEN_OPTIONS. The provider supports READ, WRITE, APPEND, TRUNCATE_EXISTING, CREATE, CREATE_NEW, DELETE_ON_CLOSE, SPARSE, SYNC, DSYNC, and LinkOption.NOFOLLOW_LINKS; the list must stay in sync with the guest-side Target_*_TruffleFileSystemProvider. Anything else (a custom OpenOption) has no bitmask slot.

Source

Thrown at espresso/src/com.oracle.truffle.espresso.io/src/sun/nio/fs/TruffleFileSystemProvider.java:224

    private static int copyOptionsToMask(CopyOption... options) {
        int mask = 0;
        for (CopyOption option : options) {
            int index = SUPPORTED_COPY_OPTIONS.indexOf(option);
            if (index < 0) {
                throw new UnsupportedOperationException("copy option: " + option);
            }
            assert index < 32;
            mask |= 1 << index;
        }
        return mask;
    }

    private static int openOptionsToMask(Set<? extends OpenOption> options) {
        int mask = 0;
        for (OpenOption option : options) {
            int index = SUPPORTED_OPEN_OPTIONS.indexOf(option);
            if (index < 0) {
                throw new UnsupportedOperationException("open option: " + option);
            }
            mask |= 1 << index;
        }
        return mask;
    }

    @Override
    public void copy(Path source, Path target, CopyOption... options) throws IOException {
        copy0(TrufflePath.toTrufflePath(source), TrufflePath.toTrufflePath(target), copyOptionsToMask(options));
    }

    @Override
    public void move(Path source, Path target, CopyOption... options) throws IOException {
        move0(TrufflePath.toTrufflePath(source), TrufflePath.toTrufflePath(target), copyOptionsToMask(options));
    }

    @Override
    public boolean isSameFile(Path path, Path path2) throws IOException {

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Remove the custom OpenOption from the set before opening the channel
  2. Check option instanceof StandardOpenOption or LinkOption.NOFOLLOW_LINKS in generic wrappers and drop unknown ones
  3. For unsupported behavior (e.g. custom buffering), wrap the resulting channel instead of using an option

Example fix

// before
Files.newByteChannel(path, Set.of(CREATE, WRITE, new DirectIoOption()));

// after
Files.newByteChannel(path, Set.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE));
Defensive patterns

Strategy: validation

Validate before calling

private static final Set<OpenOption> TRUFFLE_OPEN_OPTIONS = Set.of(
    StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.APPEND,
    StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE, StandardOpenOption.CREATE_NEW,
    StandardOpenOption.DELETE_ON_CLOSE, StandardOpenOption.SPARSE, StandardOpenOption.SYNC,
    StandardOpenOption.DSYNC, LinkOption.NOFOLLOW_LINKS);
options.removeIf(o -> !TRUFFLE_OPEN_OPTIONS.contains(o));

Try / catch

catch (UnsupportedOperationException e) when message starts with 'open option': retry with options filtered to the eleven supported constants.

Prevention

When it happens

Trigger: Files.newByteChannel/newFileChannel/newOutputStream/newInputStream on a TrufflePath with a user-defined OpenOption implementation, since all eleven standard StandardOpenOption values are supported.

Common situations: Libraries that define their own OpenOption (e.g. for direct-I/O or compression hints) and pass it through Files.*; code migrating from another provider that accepts extended options.

Related errors


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/f3bf6e86d5358cff. Report an issue: GitHub.