Understanding the jdeps Dependency Puzzle

When creating custom build tools, Maven plugins, or Gradle plugins, you often need to programmatically analyze a JAR file's system dependencies—specifically standard system modules like java.* and jdk.* (sometimes called JDK system modules). Java provides a CLI utility and API called jdeps specifically for static dependency analysis.

However, running a command intended to print module dependencies without pulling in third-party libraries often fails:

jdeps --print-module-deps --ignore-missing-deps target.jar

Instead of listing the standard JDK modules, it throws an error and aborts:

Error: Module xxx.yyy not found, required by x.y.z

This leaves many developers wondering why --ignore-missing-deps seems to be ignored.

Why --print-module-deps Fails on Missing Dependencies

The issue stems from how --print-module-deps operates compared to basic bytecode analysis. The --print-module-deps flag triggers standard Java Module System resolution. If your target JAR (or an explicit module inside it) contains a module-info.class file that explicitly requires a missing module, the Module System aborts resolution immediately before jdeps can inspect individual class files.

The --ignore-missing-deps flag only suppresses missing dependency errors during class-level bytecode analysis; it cannot bypass hard failures during explicit module graph resolution.

Solution 1: Use Summary Mode (-s) with --ignore-missing-deps

To bypass strict module graph resolution and analyze only available classes, replace --print-module-deps with summary mode (-s or --summary):

jdeps -s --ignore-missing-deps target.jar

This forces jdeps to perform class-file bytecode scanning rather than JPMS module resolution. The command will output dependency mappings without failing on missing external libraries, allowing you to filter out JDK modules easily.

Solution 2: Programmatic Execution via ToolProvider in Java

Since you are developing a Maven or Gradle plugin, executing jdeps programmatically using Java's built-in ToolProvider API is the most reliable approach. You can pass the summary arguments and parse the output in-memory without spawning external OS processes.

Here is a complete Java snippet demonstrating this approach:

import java.io.PrintWriter;import java.io.StringWriter;import java.util.Arrays;import java.util.Set;import java.util.spi.ToolProvider;import java.util.stream.Collectors;public class ModuleDependencyExtractor {    public static Set<String> getJdkModules(String jarPath) {        ToolProvider jdeps = ToolProvider.findFirst("jdeps")                .orElseThrow(() -> new IllegalStateException("jdeps tool not found in JDK"));        StringWriter out = new StringWriter();        PrintWriter pw = new PrintWriter(out);        // Run jdeps with summary mode and ignore missing deps        int result = jdeps.run(pw, pw, "-s", "--ignore-missing-deps", jarPath);        if (result != 0) {            throw new RuntimeException("jdeps execution failed: " + out.toString());        }        // Parse output lines to extract standard JDK dependencies        return Arrays.stream(out.toString().split("\\R"))                .map(String::trim)                .filter(line -> line.contains("->"))                .map(line -> line.substring(line.indexOf("->") + 2).trim())                .filter(target -> target.startsWith("java.") || target.startsWith("jdk."))                .collect(Collectors.toSet());    }}

Summary Best Practices

  • Avoid --print-module-deps when analyzing partial codebases or individual JARs missing their full module path.
  • Use -s (or --summary) combined with --ignore-missing-deps to fallback to class-level bytecode analysis.
  • Leverage ToolProvider.findFirst("jdeps") for clean, in-process execution inside Maven or Gradle plugins.