oracle/graal · error · IllegalArgumentException

READ + APPEND not allowed

Error message

READ + APPEND not allowed

What it means

Thrown by TruffleFileSystemProvider.newFileChannel when the option set contains both READ and APPEND. Opening for append positions every write at the end of the file, which is meaningless for a channel that is also readable, so the combination is rejected up front exactly as the JDK's sun.nio.fs providers do. Note that APPEND already implies WRITE.

Source

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

        if (!readable && !writable) {
            if (append) {
                writable = true;
            } 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);
    }

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Drop READ and open with just APPEND (it implies WRITE) if you only append
  2. Open with READ + WRITE and seek to the end manually if you need to read back what you wrote
  3. Build the option set conditionally so READ is never added when APPEND is present

Example fix

// before
FileChannel ch = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.APPEND);

// after
FileChannel ch = FileChannel.open(path, StandardOpenOption.WRITE, StandardOpenOption.APPEND);
// or, to read and append:
FileChannel rw = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE);
rw.position(rw.size());
Defensive patterns

Strategy: validation

Validate before calling

Set<OpenOption> opts = ...;
if (opts.contains(StandardOpenOption.READ) && opts.contains(StandardOpenOption.APPEND)) {
    opts.remove(StandardOpenOption.READ); // APPEND implies WRITE
}

Try / catch

catch (IllegalArgumentException e) when message contains 'READ + APPEND': rethrow as a configuration error naming the offending option set.

Prevention

When it happens

Trigger: Files.newByteChannel(path, StandardOpenOption.READ, StandardOpenOption.APPEND) or Files.newFileChannel(...) with both flags, executed against a TrufflePath in the Espresso guest.

Common situations: Log-rotation code that opens a file with a generic option list built at runtime and accidentally unions READ into an append set; refactoring code that previously used RandomAccessFile.

Related errors


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