Upgrading a Java application to a newer JDK can expose problems that remain hidden during normal development. An application may compile and run successfully on its current version but fail after an upgrade because it depends on an internal JDK API, an outdated library, or a module that is no longer available.
These problems are not always obvious from the application's source code. A dependency may come from a third-party JAR, an older framework, or a library that has not been maintained for years.
Java provides a command-line tool called jdeps to analyze dependencies at the package, class, and module levels. It can help developers identify which Java APIs an application uses and determine whether those dependencies belong to supported modules or internal JDK implementations.
Running jdeps before an upgrade provides an early warning system. It does not guarantee that an application will work on the target JDK, but it helps identify dependency problems before they become production failures.
What Is jdeps?
jdeps is a dependency analysis tool included in the JDK. It examines Java class files and JAR archives to report dependencies between classes and packages.
For example, an application may use classes from these packages:
import java.util.ArrayList;
import java.sql.Connection;
import sun.misc.Unsafe;
The first two imports refer to standard Java APIs. The third refers to an internal or non-standard API whose availability and behavior may vary across JDK releases.
A dependency analyzer can help identify such usage, including dependencies that originate from compiled libraries rather than directly from application source code.
Developers commonly use jdeps to:
- Identify dependencies on internal JDK APIs.
- Understand package and module dependencies.
- Inspect third-party JAR files before a JDK upgrade.
- Generate dependency reports for migration planning.
- Check whether an application depends on modules that are not included in a reduced runtime image.
The tool is useful during routine maintenance as well as major Java upgrades.
Why Hidden Dependencies Matter
The Java platform has changed significantly across releases. One important change was the introduction of the Java Platform Module System in Java 9.
The module system made platform boundaries more explicit and exposed applications that depended on implementation details rather than supported APIs.
Some internal APIs were encapsulated, restricted, or removed. Code that relied on them could encounter compilation errors, access exceptions, warnings, or runtime failures after an upgrade.
Consider a library that uses an internal JDK class to perform low-level memory operations. The application team might never call that class directly, yet the dependency could still affect the application when the library initializes.
There are three common sources of hidden dependencies:
- Application code that directly references internal APIs.
- Third-party libraries that use unsupported JDK classes.
- Reflection or runtime-generated code that is not fully visible to static analysis.
jdeps is particularly useful for the first two categories, although it cannot detect every possible runtime dependency.
Running jdeps Against an Application
Before using jdeps, confirm that the JDK containing the tool is installed and available on the command line.
Run:
jdeps --version
This prints the tool's version.
If the command is unavailable, check the installed JDK and the system's PATH configuration. A Java Runtime Environment or a restricted runtime image may not include the full set of development tools.
Analyze a JAR File
Suppose an application is packaged as:
customer-service.jar
Run:
jdeps customer-service.jar
The output reports package dependencies found in the archive.
This is a useful first check, but a basic report does not necessarily provide every detail needed for a migration. For a more complete review, analyze the application's dependencies and select the reporting options appropriate for the investigation.
Display Class-Level Dependencies
To see dependencies at the class level, use:
jdeps -verbose:class customer-service.jar
This produces more detailed output than a package-level report.
Class-level information can help identify which application classes reference particular APIs. That makes it easier to locate the code that needs attention.
The trade-off is output volume. Large applications can produce lengthy reports, so it may be more practical to save the results to a file:
jdeps -verbose:class customer-service.jar \
> jdeps-report.txt
The report can then be searched, reviewed, or stored as part of the upgrade documentation.
Finding Internal JDK API Dependencies
One of the most useful checks before a JDK upgrade is identifying references to internal APIs.
Run:
jdeps --jdk-internals customer-service.jar
The --jdk-internals option reports dependencies on internal JDK APIs that the tool can identify.
For example, the report might indicate that a dependency references sun.misc.Unsafe or another internal API.
The exact output depends on the JDK version, the classes being analyzed, and the contents of the archive.
An internal API warning should be investigated rather than treated as an automatic upgrade failure. Some dependencies may have supported alternatives, while others may require a library upgrade or a larger code change.
What to Do When an Internal API Is Found
Start by identifying which component introduces the dependency.
If the dependency originates in application code, search the source code for the reported class or package and identify why it is being used.
If it originates in a third-party library, determine which JAR contains the reference. Then check whether a newer library version removes the dependency or provides a supported replacement.
For example, code that uses internal collection or reflection mechanisms may be replaceable with public Java APIs. Low-level libraries may require more specialized investigation.
Do not assume that suppressing a warning makes the code compatible with future JDK versions.
Analyzing Dependencies in Third-Party Libraries
A JAR file can depend on internal APIs even when the application code does not.
Suppose an application contains these dependencies:
customer-service.jar
logging-library.jar
legacy-reporting.jar
database-driver.jar
Analyze the application and relevant libraries individually:
jdeps --jdk-internals customer-service.jar
jdeps --jdk-internals legacy-reporting.jar
This can help narrow down which component introduces a dependency.
For a directory containing multiple JAR files, a shell loop can automate the process:
for file in lib/*.jar; do
echo "Analyzing $file"
jdeps --jdk-internals "$file"
done
This example uses a Unix-like shell. Windows users can use an equivalent PowerShell script or run the commands individually.
For reliable results, make sure the analysis covers the libraries actually packaged and deployed with the application. A local dependency directory may not exactly match the production artifact.
Checking Java Module Dependencies
The module system provides another useful view of application dependencies.
Use:
jdeps --list-deps customer-service.jar
This reports module dependencies that jdeps can determine for the analyzed application.
For more detail, use:
jdeps --list-reduced-deps customer-service.jar
The reduced dependency view can help simplify the module dependency report by excluding certain transitive relationships.
These reports are useful when investigating modular applications or preparing a custom runtime image. However, they should not be treated as a complete description of every runtime dependency, especially when an application uses reflection, service loading, or dynamic class loading.
Check for Missing Dependencies
If a dependency is not found during analysis, provide the relevant classpath or module path where appropriate.
For example:
jdeps \
--class-path "lib/*" \
customer-service.jar
This allows jdeps to resolve dependencies from the specified libraries.
A missing dependency can affect the accuracy of the report. If the tool cannot locate a class, it cannot always determine the full dependency relationship.
Review unresolved references before drawing conclusions from the output.
Comparing Dependencies Before and After an Upgrade
A practical migration process should analyze both the existing build and the build intended for the target JDK.
For example:
jdeps --jdk-internals \
customer-service-old.jar \
> old-dependencies.txt
Then analyze the upgraded artifact:
jdeps --jdk-internals \
customer-service-new.jar \
> new-dependencies.txt
Compare the reports to identify internal API dependencies that remain or have been introduced.
This comparison is particularly useful when the upgrade also changes framework versions, database drivers, or other third-party libraries.
The analysis should be performed against the actual compiled artifacts. Source-code changes alone do not prove that the resulting application has removed a dependency.
Using jdeps With Other Upgrade Checks
jdeps is a static analysis tool. It examines class and module dependencies, but it cannot predict every runtime failure.
For a complete JDK migration, combine its results with other checks.
| Tool or test | What it helps identify |
|---|
jdeps | Class, package, module, and detectable internal API dependencies |
| Java compiler | Source incompatibilities and compilation errors |
| Automated tests | Behavioral changes and application regressions |
| Integration tests | Problems involving databases, frameworks, and external services |
| Runtime testing | Reflection failures, configuration issues, and environment-specific behavior |
| Dependency analysis tools | Outdated libraries and known dependency vulnerabilities |
For example, jdeps may identify a library that references an internal API, but only an application test may reveal whether the affected code path is actually exercised during startup or normal operation.
Common Mistakes
Analyzing Only the Application JAR
The main application JAR may not contain the problematic dependency. A third-party library can introduce the same issue.
Analyze the relevant dependency artifacts and ensure that the report represents the production application.
Treating a Clean Report as a Guarantee
A report without internal API warnings does not prove that the application will work on the target JDK.
Reflection, dynamically generated bytecode, native libraries, service loading, and runtime configuration can introduce dependencies that static analysis does not fully reveal.
Use jdeps as one part of a broader migration process.
Ignoring Unresolved Classes
If dependencies cannot be resolved, the analysis may be incomplete.
Provide the correct classpath or module path, inspect missing references, and repeat the analysis before concluding that the dependency graph is safe.
Upgrading Without Testing the Actual Artifact
The JAR analyzed locally may differ from the one deployed in production.
Run dependency analysis against the release artifact produced by the same build process used for deployment. This reduces the risk of missing dependencies introduced by packaging differences.
Best Practices for Production Upgrades
- Run
jdeps --jdk-internals before starting a major JDK migration. - Analyze application code and third-party libraries.
- Include the correct classpath or module path when needed.
- Save reports so that changes can be reviewed between releases.
- Investigate internal API references instead of suppressing warnings without a plan.
- Update outdated dependencies where supported alternatives are available.
- Combine static analysis with compilation, integration tests, and runtime testing.
- Repeat the analysis against the final packaged artifact.
- Document unresolved dependencies and assign clear ownership for fixing them.
Summary
Java's jdeps tool helps developers understand application dependencies before upgrading the JDK. It can reveal internal API usage, show package and module relationships, and identify dependencies that need further investigation.
The most valuable starting point is to run jdeps --jdk-internals against the application and its important third-party libraries. Then review the results, resolve missing dependencies, and compare reports across builds.
A successful JDK upgrade requires more than a clean dependency report. Combine jdeps with compilation, automated testing, and runtime validation to identify compatibility problems before they reach production.
Join the conversation! Your thoughts help the community grow.