oracle/graal · error · UnsupportedOperationException

copy option: {}

Error message

copy option: {}

What it means

Thrown by TruffleFileSystemProvider.copyOptionsToMask when a CopyOption passed to copy() or move() is not in SUPPORTED_COPY_OPTIONS. Only REPLACE_EXISTING, COPY_ATTRIBUTES, ATOMIC_MOVE, and LinkOption.NOFOLLOW_LINKS are forwarded to the native layer; any other implementation of CopyOption (the interface has only those standard constants) cannot be represented in the option bitmask.

Source

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

                    StandardOpenOption.DELETE_ON_CLOSE,
                    StandardOpenOption.SPARSE,
                    StandardOpenOption.SYNC,
                    StandardOpenOption.DSYNC,
                    LinkOption.NOFOLLOW_LINKS);

    // Keep in sync with Target_*_TruffleFileSystemProvider#SUPPORTED_COPY_OPTIONS.
    private static final List<CopyOption> SUPPORTED_COPY_OPTIONS = List.of(
                    StandardCopyOption.REPLACE_EXISTING,
                    StandardCopyOption.COPY_ATTRIBUTES,
                    StandardCopyOption.ATOMIC_MOVE,
                    LinkOption.NOFOLLOW_LINKS);

    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;
    }

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Restrict options to StandardCopyOption.REPLACE_EXISTING / COPY_ATTRIBUTES / ATOMIC_MOVE and LinkOption.NOFOLLOW_LINKS
  2. If ATOMIC_MOVE is rejected downstream on cross-store moves, retry without it
  3. Filter unknown options out (with a warning) before calling copy/move in generic utility code

Example fix

// before
Files.copy(src, dst, new MyFastCopyOption()); // custom CopyOption

// after
Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
Defensive patterns

Strategy: validation

Validate before calling

private static final Set<CopyOption> TRUFFLE_COPY_OPTIONS = Set.of(
    StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES,
    StandardCopyOption.ATOMIC_MOVE, LinkOption.NOFOLLOW_LINKS);
for (CopyOption o : options) {
    if (!TRUFFLE_COPY_OPTIONS.contains(o)) throw new UnsupportedOperationException("copy option: " + o);
}

Try / catch

catch (UnsupportedOperationException e) { log and retry Files.copy(src, dst) with no options; }

Prevention

When it happens

Trigger: Calling Files.copy/Files.move on TrufflePaths with a custom CopyOption implementation, or a third-party library passing its own option objects.

Common situations: Cross-filesystem utilities written against other providers (e.g. a custom extended-attributes copy option) reused against the Truffle file system; option objects from a different class loader failing equals() against the standard constants.

Related errors


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