Study guide
12.5 Compiling and Packaging Code: Non-Modular JARs, Modular JARs, and Migration Strategies
Oracle Java SE 17 Developer Certified Professional 1Z0-829
Track your progress as you read
Finished sections get a check mark.
1 Introduction: The 1Z0-829 Exam
About the Java SE 17 Developer (1Z0-829) Exam
2 Chapter 1: Java Fundamentals & Language Basics
1.1 Primitive Types, Literals, Wrapper Classes, and Type Promotion/Casting 1.2 Local Variable Type Inference (`var`) Rules and Scoping 1.3 Operators, Precedence, Evaluation Order, and Short-Circuit Evaluation 1.4 Text Blocks, String Immutability, and StringBuilder APIs
3 Chapter 2: Program Flow & Decision Constructs
2.1 Conditional Branching with if/else and the Ternary Operator 2.2 Switch Statements and Switch Expressions 2.3 Iteration Constructs, Loop Control, and Branching Labels
4 Chapter 3: Object-Oriented Design & Class Mechanics
3.1 Class Declarations, Field/Method Signatures, and Access Modifiers 3.2 Constructors, Overloading, Initialization Order, and Garbage Collection 3.3 Inheritance, Method Overriding, Polymorphism, and `super`/`this` 3.4 Abstract Classes, Static Members, and Object Methods (`equals`, `hashCode`, `toString`)
5 Chapter 4: Advanced OOP: Interfaces, Records & Sealed Types
4.1 Interface Architecture: Default, Static, and Private Methods 4.2 Enumerations: Constants, Constructors, Methods, and Custom Bodies 4.3 Records: Canonical/Compact Constructors, Components, and Immutability 4.4 Sealed Classes and Interfaces: `permits`, Hierarchy Constraints, and Modifiers
6 Chapter 5: Pattern Matching & Nested Types
5.1 Pattern Matching for `instanceof` and Flow Scoping Mechanics 5.2 Nested Types: Member Inner, Static Nested, Local, and Anonymous Classes 5.3 Defensive Programming, Immutability Patterns, and Validation
7 Chapter 6: Generics, Collections & Arrays
6.1 Generic Classes, Interfaces, Methods, and Type Erasure 6.2 Bounded Type Parameters and Wildcards (PECS Rule) 6.3 Collections Framework: List, Set, Queue, and Deque APIs 6.4 The Map Interface, Sorting, Comparable, and Comparator 6.5 Arrays: Declaration, Initialization, Multidimensional Arrays, and the Arrays Utility Class
8 Chapter 7: Functional Programming & Lambda Expressions
7.1 Functional Interfaces and Lambda Syntax Rules 7.2 Standard Functional Interfaces in `java.util.function` 7.3 Method References: Static, Bound, Unbound, and Constructor References
9 Chapter 8: The Stream API
8.1 Stream Lifecycle: Creation, Pipeline Architecture, and Lazy Evaluation 8.2 Intermediate Stream Operations: Filtering, Mapping, Sorting, and Slicing 8.3 Terminal Stream Operations: Reduction, Matching, Finding, and Collecting 8.4 Primitive Streams (`IntStream`, `LongStream`, `DoubleStream`) and Numeric Statistics
10 Chapter 9: Advanced Streams & Collectors
9.1 The Optional Class: Safe Null Handling, Transformation, and Chaining 9.2 The Collectors API: Downstream Reductions, Grouping, and Partitioning 9.3 Parallel Streams: Fork/Join Pool, Spliterators, and Concurrency Pitfalls
11 Chapter 10: Exception Handling
10.1 The Java Exception Hierarchy: Checked vs. Unchecked Exceptions and Errors 10.2 Exception Constructs: `try-catch-finally`, Multi-Catch, and Rethrowing 10.3 Try-With-Resources, `AutoCloseable`, Suppressed Exceptions, and Custom Exceptions
12 Chapter 11: Date/Time API & Localization
11.1 Date and Time Core Types: `LocalDate`, `LocalTime`, `LocalDateTime` 11.2 Timestamps, Intervals, and Durations: `Instant`, `Period`, and `Duration` 11.3 Time Zones, Daylight Saving Time (DST) Offsets, and `ZonedDateTime` 11.4 Internationalization and Localization: `Locale`, `ResourceBundle`, and Formatters 11.5 Formatting and Parsing Messages, Numbers, Percentages, and Localized Dates
13 Chapter 12: Java Platform Module System (JPMS)
12.1 Module Architecture, `module-info.java`, and Module Paths 12.2 Module Directives: `requires`, `requires transitive`, `exports`, and Qualified Exports 12.3 Reflection, Deep Encapsulation, `opens`, and Open Modules 12.4 Services in JPMS (`uses`, `provides...with`), Migration Paths, and Tooling (`jlink`, `jdeps`) 12.5 Compiling and Packaging Code: Non-Modular JARs, Modular JARs, and Migration Strategies
14 Chapter 13: Concurrency & Multithreading
13.1 Thread Fundamentals, `Runnable`, `Callable`, and Thread Lifecycle 13.2 Memory Visibility, `volatile`, Intrinsic Locks, and Synchronization 13.3 The Concurrency Framework: `ExecutorService`, Thread Pools, and `Future` 13.4 Concurrent Collections, Atomic Variables, Explicit Locks, and `CompletableFuture`
15 Chapter 14: Java I/O and NIO.2
14.1 Classical I/O: Byte and Character Streams, Readers, Writers, and Buffering 14.2 Object Serialization, `Serializable`, `transient`, and `serialVersionUID` 14.3 NIO.2 Architecture: `Path`, `Paths`, and Basic `Files` Operations 14.4 Advanced NIO.2: Directory Walking, Streams (`Files.lines`, `walk`), and `WatchService`
16 Chapter 15: Accessing Databases with JDBC
15.1 JDBC Architecture, Drivers, `DriverManager`, and `DataSource` Connections 15.2 SQL Execution: `Statement`, `PreparedStatement`, and `CallableStatement` 15.3 `ResultSet` Navigation, Data Types, and Transaction Management (Commit, Rollback, Savepoints)
17 Chapter 16: Annotations & the Java Logging API
16.1 Standard Annotations: @Override, @FunctionalInterface, @Deprecated, @SuppressWarnings, and @SafeVarargs 16.2 Java Logging API Basics: Logger, Levels, Handlers, and Configuration
12.5 Compiling and Packaging Code: Non-Modular JARs, Modular JARs, and Migration Strategies
Key Takeaways Explain This Topic AI
jar cfe app.jar com.x.Main -C classes . creates a JAR whose manifest Main-Class lets java -jar app.jar run it.
A modular JAR placed on the classpath runs in the unnamed module because its module-info.class is ignored.
A non-modular JAR on the module path becomes an automatic module, so legacy-tools-2.1.jar is named legacy.tools.
Bottom-up migration converts dependency-free libraries into named modules first while unmigrated code stays on the classpath.
Top-down migration places every JAR on the module path as an automatic module and then modularizes the application first.
Last updated: October 2026
12.5 Compiling and Packaging Code: Non-Modular JARs, Modular JARs, and Migration Strategies
The second module objective is practical: "Compile Java code, produce modular and non-modular jars, runtime images, and implement migration using unnamed and automatic modules." Sections 12.1 and 12.4 covered modular compilation, jlink , and the module types. This section adds the classpath workflow, shows how a JAR's location decides its module type, and walks through the two official migration strategies.
Compiling and Running Non-Modular Code
Classic classpath code has no module-info.java : # Compile: -d sets the output root; package folders are created under it
javac -d classes src/com/x/Main.java
- # Run from the class directory (fully qualified class name, no .class suffix)
- java -cp classes com.x.Main
- # Java 11+ single-file source launcher: compiles in memory and runs
- java Hello.java arg1 arg2
The classpath can be given as -cp , -classpath , or --class-path , and it lists directories or JAR files separated by : (Linux and macOS) or ; (Windows).
Creating a Non-Modular JAR # Long options
jar --create --file app.jar --main-class com.x.Main -C classes .
# Equivalent short options: c = create, f = file, e = entry point (Main-Class)
jar cfe app.jar com.x.Main -C classes .
- java -jar app.jar # uses the Main-Class attribute in META-INF/MANIFEST.MF
- java -cp app.jar com.x.Main # names the main class explicitly
-C classes . tells jar to change into classes and add everything there, so the JAR entries start at com/x/Main.class . When you run java -jar , the JVM ignores any -cp option and uses only the JAR plus its manifest Class-Path entries.
A modular JAR is built with the same commands; the only difference is that module-info.class sits at the root of the JAR (see 12.1 for jar --create --file ... --main-class ... -C out/module . ).
Where a JAR Goes Decides Its Module Type
- JAR contents Placed on the module path ( -p ) Placed on the classpath ( -cp )
- Has module-info.class (modular) Named module with its declared name Unnamed module (the descriptor is ignored)
No module-info.class (non-modular) Automatic module (name from Automatic-Module-Name or the file name) Unnamed module
You can confirm this at runtime: a class from a modular JAR run with java -cp mapp.jar com.app.App reports getModule().getName() as null , while java -p mods -m com.app reports com.app . For a non-modular file named legacy-tools-2.1.jar on the module path, jar --describe-module --file legacy-tools-2.1.jar prints legacy.tools@2.1 automatic because the version is stripped and the hyphen becomes a dot.
Useful inspection commands:
- Command Purpose
- java --list-modules Lists the modules in the current runtime (for example java.base@17... )
- java --describe-module java.sql ( -d ) Shows a module's exports and requires
- jar --describe-module --file x.jar ( -d ) Shows the descriptor of a JAR, or the derived automatic module
- java --show-module-resolution -p mods -m app Prints how the module graph was resolved at startup
jdeps -s app.jar / jdeps --jdk-internals app.jar Summarizes dependencies and finds internal JDK API use (see 12.4)
Migration Rules You Must Combine
Three facts drive every migration question:
A named module cannot read the unnamed module. You cannot write requires for classpath code, so anything a named module depends on must be on the module path.
The unnamed module reads every module. Classpath code can still use named and automatic modules.
Automatic modules read every module, including the unnamed module, and export and open all of their packages.
Bottom-up migration
Start with the lowest-level JARs, the ones that depend on nothing else in the application:
Add a module-info.java to a leaf library, exporting the packages others use, and move it to the module path as a named module .
Leave everything that has not been migrated on the classpath . That code is in the unnamed module, which can read the new named module.
Repeat, moving up the dependency graph one layer at a time, until the application module itself is named.
Bottom-up works best when you control the lower-level libraries.
Top-down migration
Start with the application and treat everything else as automatic modules:
Move all JARs to the module path. Every non-modular JAR becomes an automatic module .
Add module-info.java to the top-level application, using requires with the automatic module names (check them with jar --describe-module ).
Convert the remaining JARs to named modules later, or leave third-party libraries as automatic modules if you do not own them.
Top-down works when the application depends on third-party JARs that you cannot change. If two modules being migrated depend on each other, JPMS forbids the cycle; move the shared code into a new module that both can require.
Runtime Images for Modular and Non-Modular Applications
jlink (see 12.4) links named modules only; an application that still depends on automatic modules cannot be linked as a whole. A classpath application can still benefit: jlink --add-modules java.base,java.sql --output rt builds a smaller runtime containing only the JDK modules it needs, and rt/bin/java -cp app.jar com.x.Main runs the classpath code on that runtime.