skylot/jadx · error · RuntimeException

Failed to save JSON file: {}

Error message

Failed to save JSON file: {}

What it means

Thrown by CallGraphExportDot.writeTo(Path) as a plain RuntimeException wrapping an IOException raised while writing the call-graph DOT/JSON export to disk. writeTo makes parent dirs for the file, then writes the string with WRITE/TRUNCATE_EXISTING/CREATE.

Source

Thrown at jadx-commons/jadx-analysis/src/main/java/jadx/analysis/callgraph/CallGraphExportDot.java:41

import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;

public class CallGraphExportDot {
	private final JadxArgs args;
	private final ICallGraph callGraph;

	public CallGraphExportDot(JadxArgs args, ICallGraph callGraph) {
		this.args = args;
		this.callGraph = callGraph;
	}

	public void writeTo(Path path) {
		try {
			FileUtils.makeDirsForFile(path);
			Files.writeString(path, writeToString(), StandardCharsets.UTF_8,
					WRITE, TRUNCATE_EXISTING, CREATE);
		} catch (IOException e) {
			throw new RuntimeException("Failed to save JSON file: " + path, e);
		}
	}

	public String writeToString() {
		// collect nodes
		Map<Integer, Node> nodeMap = new HashMap<>();
		for (ICallGraphEdge edge : callGraph.edges()) {
			addNode(edge.from(), nodeMap);
			addNode(edge.to(), nodeMap);
		}
		List<Node> nodes = new ArrayList<>(nodeMap.values());
		nodes.sort(Comparator.comparingInt(o -> o.id));

		SimpleCodeWriter cw = new SimpleCodeWriter(args);
		cw.add("digraph CallGraph {");
		for (Node node : nodes) {
			cw.startLine();
			addNodeName(cw, node.id);

View on GitHub (pinned to e738a26571)

Solutions

  1. Ensure the target directory exists and is writable.
  2. Use an absolute path to avoid working-directory ambiguity.
  3. Free disk space.
  4. Check the wrapped IOException for the precise OS-level reason.

Example fix

// before
exporter.writeTo(Path.of("/readonly/out.dot"));
// after
Files.createDirectories(Path.of("/tmp/cg"));
exporter.writeTo(Path.of("/tmp/cg/out.dot"));
Defensive patterns

Strategy: validation

Validate before calling

Path parent = path.getParent();
if (parent == null || (!Files.exists(parent) && !parent.toFile().mkdirs())) {
    throw new IllegalStateException("Cannot create output dir for: " + path);
}
if (!Files.isWritable(parent)) {
    throw new IllegalStateException("Output dir not writable: " + parent);
}

Type guard

public static boolean isOutputWritable(Path p) {
    Path parent = p.getParent();
    return parent != null && Files.isWritable(parent);
}

Try / catch

try {
    export.writeTo(path);
} catch (RuntimeException e) {
    if (e.getMessage().startsWith("Failed to save JSON file")) {
        LOG.error("Cannot write call-graph export to {}", path, e.getCause());
        // retry to a temp location
        export.writeTo(Files.createTempFile("callgraph", ".dot"));
    } else throw e;
}

Prevention

When it happens

Trigger: Calling CallGraphExportDot.writeTo(path) where path cannot be created or written: parent directory missing/non-creatable, permission denied, disk full, or the path is invalid. Note: the message says 'JSON file' even though this exporter writes DOT-format content.

Common situations: Output directory not writable; disk full; path points to a location that needs elevated permissions; relative path resolved against an unexpected working directory.

Related errors


AI-assisted analysis of skylot/jadx@e738a26571 (2026-08-14). Data as JSON: /api/errors/bd3f5870f3cf109c. Report an issue: GitHub.