junit-team/junit5 · error · UncheckedIOException
Failed to write report
Error message
Failed to write report
What it means
Thrown by the internal ApiReportGenerator tool (used to produce the JUnit API status report) when an I/O error occurs while writing the generated report to its output stream. It wraps the underlying IOException in an UncheckedIOException so the forEach lambda can propagate a checked exception. This is build/tooling code, not part of the public test API.
Solutions
- Check that the output path/directory the generator is asked to write to exists and is writable before running the tool.
- If you passed a custom StreamOpener, ensure openStream() returns a fresh, open, writable stream and that no prior consumer closed it.
- Read the wrapped IOException (getCause()) for the real filesystem reason (permission, ENOSPC, not-a-directory) and address that directly.
- Run the generator with explicit/redirected output or the default EXPERIMENTAL target (no args) to isolate whether the failure is path-specific.
Example fix
// before: opener points at an unwritable path
@ParameterizedTest
...
// after: preflight the destination
Path out = Path.of("build/api-report");
Files.createDirectories(out);
if (!Files.isWritable(out)) {
throw new IllegalStateException("Report dir not writable: " + out);
} Defensive patterns
Strategy: validation
Validate before calling
Path out = resolveOutputPath();
Files.createDirectories(out);
if (!Files.isWritable(out)) {
throw new IllegalStateException("Output not writable: " + out);
} Try / catch
try {
generator.run(args);
} catch (UncheckedIOException e) {
IOException cause = e.getCause();
// handle filesystem-level cause (permission, ENOSPC, closed stream)
} Prevention
- Validate the output destination is writable before invoking the generator.
- Return fresh, open streams from any custom StreamOpener.
- Treat the wrapped IOException as the source of truth for the real failure.
When it happens
Trigger: Running the ApiReportGenerator main entry with an output target whose StreamOpener.openStream() fails (e.g. the stream's writer throws IOException mid-write), or the destination is unwritable/closed. The catch in the try-with-resources around the opener.openStream()/PrintWriter write path converts any IOException to this UncheckedIOException.
Common situations: Pointing the report at a directory that doesn't exist or lacks write permission; a closed/already-consumed stream returned by a custom StreamOpener; running the tool from a sandbox with restricted filesystem access; disk-full or path-too-long during large report writes.
Related errors
- Failed to copy files to the output directory
- Error redirecting stderr to file
- Error redirecting stdout and stderr to file
- Error redirecting stdout to file
- Failed to close file system for
AI-assisted analysis of junit-team/junit5@f070c699a0 (2026-08-11).
Data as JSON: /api/errors/11f3c5649b99f535.
Report an issue: GitHub.
Appendix: source
Thrown at documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java:79
var apiReport = generateReport(scanResult);
// ApiReportWriter reportWriter = new MarkdownApiReportWriter(apiReport);
ApiReportWriter reportWriter = new AsciidocApiReportWriter(apiReport);
// ApiReportWriter reportWriter = new HtmlApiReportWriter(apiReport);
// reportWriter.printReportHeader(new PrintWriter(System.out, true));
// Print report for all Usage enum constants
// reportWriter.printDeclarationInfo(new PrintWriter(System.out, true), EnumSet.allOf(Status.class));
// Print report only for specific Status constants, defaults to only EXPERIMENTAL
parseArgs(args).forEach((status, opener) -> {
try (var stream = opener.openStream()) {
var writer = new PrintWriter(stream == null ? System.out : stream, true, UTF_8);
reportWriter.printDeclarationInfo(writer, EnumSet.of(status));
}
catch (IOException e) {
throw new UncheckedIOException("Failed to write report", e);
}
});
}
}
// -------------------------------------------------------------------------
private static Map<Status, StreamOpener> parseArgs(String[] args) {
Map<Status, StreamOpener> outputByStatus = new EnumMap<>(Status.class);
if (args.length == 0) {
outputByStatus.put(Status.EXPERIMENTAL, () -> null);
}
else {
Arrays.stream(args) //
.map(arg -> arg.split("=", 2)) //
.forEach(parts -> outputByStatus.put(//
Status.valueOf(parts[0]), //
() -> parts.length < 2 //View on GitHub (pinned to f070c699a0)