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
- Remove TRUNCATE_EXISTING wherever APPEND is requested
- Model the two modes explicitly: write-fresh = CREATE + WRITE + TRUNCATE_EXISTING; append = CREATE + WRITE + APPEND
- 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
- Make APPEND and TRUNCATE_EXISTING mutually exclusive branches in option-building code
- Never union two caller-supplied option sets without checking this pair
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
- READ + APPEND not allowed
- open option: {}
- Path component is undefined
- Path component should be '/'
- Query component present
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/90f983d087ee34b9.
Report an issue: GitHub.