junit-team/junit5 · error · UncheckedIOException

Failed to write report

Error message

Failed to write report

What it means

Thrown as an UncheckedIOException by the ApiReportGenerator documentation build tool when writing the generated API report to an output stream fails with an IOException. This tool is an internal documentation generator (invoked via main()) that scans the org.junit packages with ClassGraph and writes Asciidoc/Markdown reports. It is not part of the runtime JUnit Jupiter API surface used by test authors.

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 956246301e)

Solutions

  1. Ensure the parent directory of the output file exists and is writable: mkdir -p $(dirname <outfile>) before running the generator.
  2. Check available disk space and write permissions on the target path.
  3. If the path argument is omitted (parseArgs returns a null opener → writes to System.out), verify stdout is not closed/redirected to an invalid target.
  4. Re-run with no argument (writes to stdout) to isolate whether the failure is path-specific.

Example fix

// before
java ApiReportGenerator EXPERIMENTAL=/nonexistent/dir/report.adoc

// after
mkdir -p /tmp/reports && java ApiReportGenerator EXPERIMENTAL=/tmp/reports/report.adoc
Defensive patterns

Strategy: try-catch

Validate before calling

Path out = Path.of(args[1]);
Path parent = out.getParent();
if (parent != null && !Files.isDirectory(parent)) {
    Files.createDirectories(parent);
}
if (!Files.isWritable(parent == null ? Path.of(".") : parent)) {
    throw new IllegalStateException("output dir not writable: " + parent);
}

Try / catch

try {
    apiReportGenerator.run(status, outputPath);
} catch (UncheckedIOException e) {
    throw new BuildException("Cannot write API report to " + outputPath + ": " + e.getCause().getMessage(), e);
}

Prevention

When it happens

Trigger: Running ApiReportGenerator with a STATUS=path argument where the path is unwritable, the parent directory does not exist, the disk is full, or the stream is closed mid-write. Also triggered if System.out itself is in a bad state (e.g., closed by a prior redirect).

Common situations: Documentation builds where the output directory was not created first, CI runners with read-only workspaces, permission denied on the target path, or a typo in the STATUS=outfile argument.

Related errors


AI-assisted analysis of junit-team/junit5@956246301e (2026-08-04). Data as JSON: /data/errors/11f3c5649b99f535.json. Report an issue: GitHub.