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

  1. Check that the output path/directory the generator is asked to write to exists and is writable before running the tool.
  2. If you passed a custom StreamOpener, ensure openStream() returns a fresh, open, writable stream and that no prior consumer closed it.
  3. Read the wrapped IOException (getCause()) for the real filesystem reason (permission, ENOSPC, not-a-directory) and address that directly.
  4. 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

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


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)