To migrate a Java application to Jigsaw, first confirm it works on your target JDK, then update its dependencies and build tools, add a module-info.java descriptor, analyze dependencies, and test on the module path. A successful compile is only one checkpoint: frameworks that use reflection may still fail at runtime unless the required packages are opened deliberately.
This guide follows a small Spring, JDBC, and ShedLock example from a 2017 Java 9 tutorial, while separating the durable migration workflow from version-specific settings that should not be copied into a current build without verification.
What does “migrate to Jigsaw” mean?
Jigsaw is the project name associated with Java’s module system, introduced with Java 9. Migrating can mean several different things: running an existing class-path application on a newer JDK, compiling against a particular Java release, or converting the application to named modules. Those are distinct milestones. The 2017 tutorial explicitly treats running on Java 9 or compiling for it as possible stopping points before adopting modules. Lukas Krecan’s DZone walkthrough is useful as a historical example, not as a current compatibility checklist.
- Run on a newer JDK: Keep the application on the class path while checking whether its existing behavior survives the JDK change.
- Compile for a Java release: Configure the build to target the intended release and API surface.
- Adopt named modules: Define a module descriptor, declare dependencies, and run with the module system’s encapsulation rules in force.
For a new migration, begin by deciding which of these outcomes you need. A named module brings explicit dependencies and stronger boundaries, but it can expose assumptions—especially reflective access—that were hidden on the class path.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchesStep 1: Establish a baseline on the target JDK
Before changing module structure or compiler settings, run the existing application and its tests on the JDK you intend to adopt. Record startup behavior, test results, warnings, and any removed or changed options. Oracle’s JDK 9 migration guide recommends running the application before recompiling and checking that behavior remains the same, not merely that the process starts. The guide is specific to Oracle JDK 9, but the baseline-first approach is broadly useful. Oracle JDK 9 Migration Guide
- Exercise important application paths, not just the startup sequence.
- Capture the exact JDK, build tool, dependency versions, and launch command used for the baseline.
- Keep this class-path result separate from later module-path results so you can identify which change introduced a failure.
Step 2: Update dependencies and build tools
Check whether every third-party library, build plugin, and IDE version supports the target JDK. Update incompatible components before interpreting module-related errors: an outdated dependency can fail for reasons unrelated to your module descriptor. Oracle’s JDK 9 guide describes migration as iterative, with library updates, compilation, and dependency analysis informing one another. For current work, consult the release and support guidance for the actual JDK and libraries you use; the 2017 guide does not establish present-day compatibility.
Do not assume that a library’s ability to run on the class path means it already has a stable, explicit module descriptor. A dependency without one may be treated as an automatic module when placed on the module path, but its derived module name can depend on its JAR filename. Krecan warns that a future module-aware artifact could change that name. Verify the module name for the precise artifact and version in your build, especially before publishing a library whose consumers will rely on it.
Rank #2
Step 3: Compile for the intended Java release
Choose compiler settings for the Java release you actually intend to support. In the tutorial’s 2017 Maven example, Krecan changes the compiler settings to Java 9 and notes that --release would be preferable in principle, but his IDE then had a limitation. That IDE limitation is historical, not current advice. Oracle’s JDK 9 migration guide recommends --release where possible instead of separate source and target settings because it also constrains the platform API surface available during compilation. Check that your current compiler plugin and IDE support the option before adopting it. Oracle’s migration guidance
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Compiler configuration and modularization are related but not interchangeable: targeting a release does not, by itself, convert a class-path application into a named module.
Step 4: Add a module descriptor and declare dependencies
Create module-info.java for the application and declare the modules it requires. Krecan’s example names its application module shedlock.example. Adding the descriptor without dependency declarations produces “package … is not visible” compiler errors because named modules must state their dependencies rather than relying on class-path visibility.
The tutorial resolves those errors by identifying the needed dependencies and adding requires declarations. Some of its dependencies are automatic modules, and its listed module names are examples from that particular 2017 build—not names to copy into another project. Inspect the actual artifacts and versions in your build to establish the names they expose.
In practice, a descriptor may contain declarations in this general form:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
module your.application.name {
requires some.dependency.module;
}
Use the real module names and only the dependencies the application needs. If you publish a library, stable dependency names matter to your consumers; filename-derived automatic names are a risk to investigate rather than assume will remain fixed.
Rank #4
Step 5: Analyze dependencies and internal JDK API use
Use jdeps to inspect static package and class dependencies, including dependencies on JDK internals. Oracle documents the -jdkinternals option and notes that jdeps can help identify replacement APIs. Prefer supported alternatives when application code or a dependency relies on internal JDK APIs. Oracle JDK 9 Migration Guide
Static analysis cannot expose every runtime access. In particular, Oracle cautions: “If the code uses reflection to call an internal API, then jdeps doesn’t warn you.” Oracle’s JDK 9 migration guide Pair dependency analysis with runtime tests, stack traces, and the library vendor’s compatibility guidance. A clean jdeps report is useful evidence, not proof that all reflective behavior will work.
Step 6: Address runtime access failures narrowly
Compilation can succeed while execution fails. In Krecan’s Java 9-era example, Spring’s reflection reaches into java.lang, which is not open to spring.core by default. The tutorial demonstrates the command-line option --add-opens java.base/java.lang=spring.core as a targeted response. That is an example tied to the sample’s JDK and Spring setup; check the behavior and framework guidance for the versions in your own application. DZone tutorial Oracle’s migration guide
Recommended Free Tools
Best Value
The sample next encounters access to an application package and demonstrates opens directives in the module descriptor. An opens directive allows reflective access to a package at runtime; an open module is broader, allowing reflective access to all packages. Prefer the narrowest package and recipient scope that satisfies the framework. A blanket open module is permissive and can undercut the encapsulation you adopted modules to gain.
When access fails, follow the exception to the specific package and accessing module. Then decide whether the lasting fix should be a library upgrade or replacement, a supported API instead of an internal one, or a narrowly scoped access grant. Oracle also documents --add-opens for acknowledging specific reflective access needs; treat command-line flags as deliberate compatibility measures, not as substitutes for understanding the access being granted.
Step 7: Test on the module path and iterate
Run the application and test suite on the module path, then address each concrete failure and repeat. The tutorial encounters successive access errors as earlier ones are resolved, illustrating why named-module migration cannot be validated by compilation alone. Test startup, important application behavior, automated tests, and deployment in the actual launch configuration.
- Launch the named module using the same JDK and dependency versions recorded for the build.
- Run tests and exercise application paths that involve frameworks, reflection, database access, and scheduled work.
- For each failure, identify whether it is a missing module dependency, unsupported internal API use, or runtime access restriction before changing the descriptor or launch flags.
- Repeat the full relevant test set after each change, and validate the deployment command as well as local execution.
Oracle’s JDK 9 guide summarizes the approach: “Migrating is an iterative process.” Successful startup is not the end of the work; it is one result to verify alongside the broader behavior and compatibility checks.
Choose the least disruptive migration path
| Goal or choice | What it means | Trade-off to consider |
|---|---|---|
| Run on a newer JDK | Keep the application on the class path while validating behavior on the target runtime. | Addresses runtime compatibility, but does not create named modules or explicit module dependencies. |
| Compile for a Java release | Configure the compiler for the intended release, using --release where supported. |
Constrains the platform API surface at compile time; it is not a substitute for module adoption. |
| Use automatic modules | Place dependencies without explicit module descriptors on the module path under derived module names. | Can ease adoption, but filename-derived names may change; verify the exact artifact and version. |
| Use explicit module descriptors | Declare module dependencies and expose only intended packages. | Offers clearer boundaries, but requires compatible dependencies and deliberate handling of reflection. |
| Grant reflective access | Use targeted opens declarations or an appropriate runtime option such as --add-opens. |
Can resolve a concrete framework need, but broad openings weaken encapsulation; an open module is the most permissive option shown in the tutorial. |
| Upgrade or replace a dependency | Move away from an old library or internal-API use toward a supported implementation. | Can avoid ongoing access workarounds, though the compatible version and migration effort depend on the specific library. |
What to take from the 2017 example
The article’s sequence—run first, update dependencies, configure compilation, declare module requirements, inspect dependencies, and resolve runtime access—remains a practical way to reason about migration. Its precise Java 9, Maven compiler, and Spring release-candidate details are historical. Do not transplant its versions, module names, IDE limitations, or flags into a current project without checking the relevant JDK and library documentation.
Krecan concluded in 2017 that migrating was possible but “Most likely not” worth it at that time, citing the state of tools and libraries. That was his opinion about the ecosystem then, not a current consensus. The appropriate decision now depends on whether your project benefits from explicit module boundaries and whether its dependencies support the runtime access it needs.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

