oracle/graal · error · IllegalArgumentException

APPEND + TRUNCATE_EXISTING not allowed

Error message

APPEND + TRUNCATE_EXISTING not allowed

What it means

Thrown by TruffleFileSystemProvider.newFileChannel when the option set contains both APPEND and TRUNCATE_EXISTING. The two options are semantically contradictory: TRUNCATE_EXISTING erases the file on open while APPEND guarantees writes land at the current end. The provider rejects the pair before opening anything, matching JDK behavior.

Source

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

            } else {
                readable = true;
            }
        }

        // set direct option
        boolean direct = false;
        for (OpenOption option : options) {
            if (ExtendedOptions.DIRECT.matches(option)) {
                direct = true;
                break;
            }
        }
        // check for Exceptions
        if (readable && append) {
            throw new IllegalArgumentException("READ + APPEND not allowed");
        }
        if (append && options.contains(StandardOpenOption.TRUNCATE_EXISTING)) {
            throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed");
        }

        FileDescriptor fd = new FileDescriptor();
        // populates fd via HostCode which opens the file and checks the permissions
        newFileChannel0(TrufflePath.toTrufflePath(path), fd, openOptionsMask, fileAttributeMask);
        return NewFileChannelHelper.open(fd, path.toString(), readable, writable, sync, direct, null);
    }

    @Override
    @SuppressWarnings("unchecked")
    public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
        // ALL_PERMISSIONS is the default permission on Unix.
        int fileAttributeMask = FileAttributeParser.parseWithDefault(FileAttributeParser.ALL_PERMISSIONS, attrs);
        createDirectory0(TrufflePath.toTrufflePath(dir), fileAttributeMask);
    }

    @Override
    public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Remove TRUNCATE_EXISTING wherever APPEND is requested
  2. Model the two modes explicitly: write-fresh = CREATE + WRITE + TRUNCATE_EXISTING; append = CREATE + WRITE + APPEND
  3. Assert the option set does not contain both flags before calling open

Example fix

// before
Set<OpenOption> opts = new HashSet<>(Set.of(CREATE, WRITE, TRUNCATE_EXISTING));
if (append) opts.add(APPEND);
FileChannel ch = FileChannel.open(path, opts);

// after
Set<OpenOption> opts = new HashSet<>(Set.of(CREATE, WRITE));
opts.add(append ? APPEND : TRUNCATE_EXISTING);
FileChannel ch = FileChannel.open(path, opts);
Defensive patterns

Strategy: validation

Validate before calling

boolean append = ...;
Set<OpenOption> opts = new HashSet<>(Set.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE));
opts.add(append ? StandardOpenOption.APPEND : StandardOpenOption.TRUNCATE_EXISTING);

Prevention

When it happens

Trigger: Files.newByteChannel(path, StandardOpenOption.APPEND, StandardOpenOption.TRUNCATE_EXISTING, ...) against a TrufflePath, typically because TRUNCATE_EXISTING was added unconditionally to a shared default option set.

Common situations: A generic 'openFile(mode)' helper that always adds TRUNCATE_EXISTING for write modes, then gets called with append=true; merging config-driven option lists without mutual-exclusion checks.

Related errors


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