Compiling and Running Java: Compact Source Files, JARs, jlink and Module Migration (1Z0-831)
Compiling and running Java code means turning source into a program the launcher can start: directly from a .java file in source-file mode, from compiled classes on the class path, from a JAR, from modules on the module path, or from a custom runtime image built with jlink. Oracle 1Z0-831 objective 7.2 covers that whole path, including the Java 25 compact source files and instance main methods, multi-file source-code programs, non-modular and modular JARs, runtime images, and migrating an application to modules through the unnamed module and automatic modules. Almost every rule here shows up as an exact command, an exact launcher error, or a program whose output depends on which main method or which module was chosen, so this lesson pairs each rule with the command or code that proves it, all run on JDK 25.
On this page10 sections
- Compact source files: a class you never declare
- Automatic imports: java.base and the java.lang.IO class
- Instance main methods and how the launcher chooses main
- Source-file mode: single-file and multi-file programs
- Compiling with javac and running with java -cp
- Non-modular JARs, the manifest and java -jar
- Modular JARs and running from the module path
- Runtime images with jlink
- The unnamed module and automatic modules
- Migration strategies and split packages
- Explain what a compact source file compiles to and which types it can use without imports, compared with an ordinary class.
- Predict which main method the launcher selects, whether an object is constructed first, and when launching fails.
- Run single-file and multi-file programs in source-file mode, placing launcher options and program arguments correctly.
- Compile with javac -d and --source-path and run classes with java -cp, diagnosing ClassNotFoundException and wrong-name errors.
- Build non-modular and modular JARs, and run them with java -jar, -cp, or -p and -m, using the manifest Main-Class, Class-Path and ModuleMainClass.
- Create a runtime image with jlink and state which modules it contains and why automatic modules are rejected.
- Migrate an application with the unnamed module and automatic modules: derive automatic module names, choose bottom-up or top-down, and resolve split packages.
Compact source files: a class you never declare
A compact source file is a .java file whose fields and methods are written at the top level, with no enclosing class; the compiler wraps them in an implicitly declared class. Compact source files and instance main methods became standard in Java 25 (JEP 512), so no preview flag is needed.
String label = "total";
int total;
void add(int n) {
total += n * 2;
}
void main() {
add(3);
add(4);
IO.println(label + "=" + total);
}Run with java Main.java, this prints total=14. The top-level declarations are ordinary members of one class: label and total are instance fields (a field is only static if you write static), add() is an instance method, and main runs on an instance the launcher creates. Records, enums and classes written at the top level become member classes of the implicit class.
The rules of the implicit class
| Property | What it means in practice |
|---|---|
final, extends Object | Nothing can subclass it; javap shows final class Main. |
| Unnamed package | A package declaration is a compile error ("compact source file should not have package declaration"). |
| Only the default constructor | Writing Main() { } is read as a method missing its return type, so it fails to compile. |
| Cannot be referenced by name | The class name is chosen by the host system from the file name, but code cannot use it: Main.total inside the file and new Main() from another file both fail with "cannot find symbol". Use the members directly instead. |
| Must have a launchable main | A compact file with no void main() or void main(String[] args) does not compile. |
Because the class has no usable name, a compact source file is a program's entry point, never a library other classes call.
Automatic imports: java.base and the java.lang.IO class
A compact source file automatically imports every package exported by the java.base module, as if it began with import module java.base;. List, ArrayList, Map, Collections, Path, Files and LocalDate all work with no import at all.
The console helper IO (with print, println and readln) is a different matter: it lives in java.lang, so every class can call IO.println(...) without an import. Its methods are not statically imported, so even in a compact file you write IO.println, not a bare println.
| Type used | Compact source file | Ordinary class declaration |
|---|---|---|
IO, String, Math (java.lang) | No import needed | No import needed |
List, Path, other java.base packages | No import needed | Needs import java.util.List; (or a module import) |
Types outside java.base (e.g. java.sql) | Needs an import | Needs an import |
So a file that wraps its code in public class Main { ... } and uses List without importing it fails to compile on List alone; IO is fine. Either import the type or remove the class wrapper so the file becomes a compact source file. Writing import java.io.IO; does not help: there is no such class there. Module import declarations themselves belong to objective 7.1 (see the modules and services lesson).
Instance main methods and how the launcher chooses main
Since Java 25, main may be an instance method, may take no parameters, and need not be public; the launcher picks one method with a fixed protocol:
- If the class declares or inherits a non-private
void main(String[] args)(the varargs formString...counts), that method is chosen. - Otherwise it looks for a non-private
void main()with no parameters. - If the chosen method is
static, it is invoked directly. If it is an instance method, the launcher first creates an object with the class's non-private no-argument constructor, then callsmainon it.
The parameter list decides first; static only decides whether an object is created. private methods are not candidates at all, so a private main(String[]) is skipped and an eligible main() runs instead. A main that returns something other than void is not a candidate either.
public class Launch {
String tag = init();
String init() {
System.out.print("[new] ");
return "ok";
}
static void main() {
System.out.println("no-arg");
}
void main(String... args) {
System.out.println(tag + " " + args.length);
}
}java Launch.java x y prints [new] ok 2: the instance main(String...) beats the static no-argument one, and because it is an instance method, construction (including field initialisers and their side effects) runs before it.
Inheritance and constructors
- An inherited
mainlaunches. IfMain extends Baseand onlyBasedeclaresvoid main(), thenjava -cp out Maincreates aMainand runs the inherited method, sogetClass()inside it reportsMain. - Inheritance does not change the order: an inherited
static void main(String[])still beats avoid main()declared in the subclass itself. - If an instance
mainis chosen but the class has no no-argument constructor (only constructors with parameters), the launcher stops beforemainruns. The wording depends on the mode: source-file mode (java Main.java) reports "can't find no argument constructor in class: Main", while class mode (javac, thenjava -cp out Main) reports "no non-private zero argument constructor found in class Main".
Source-file mode: single-file and multi-file programs
Source-file mode runs a program straight from source: java Hello.java World compiles Hello.java in memory, writes no .class files, and runs it with World as args[0]. The general form is java [options] File.java [args]: launcher options go before the file name, and everything after it is passed to main.
- Which class runs: the first top-level class declared in the file is launched, not the one whose name matches the file. A file declaring
class Aand thenclass Main, each with a main method, prints A's output. - File names are relaxed: several top-level classes may share one file, and they need not match the file name. The same file given to
javacwould fail if apublicclass's name did not match. - Source wins over stale class files (JDK 22 and later; JDK 11 to 21 refused with "class found on application class path"): if an old
Main.classsits beside an editedMain.java,java Main.javacompiles and runs the edited source, whilejava Mainruns whatever class file is on the class path.
| Command | Reads | Writes class files? |
|---|---|---|
java Main.java a b | Source, compiled in memory | No |
javac Main.java then java Main a b | The compiled Main.class on the class path | Yes (javac) |
Multi-file source-code programs
Since Java 22 (JEP 458), source-file mode can run a program spread across several .java files without compiling anything first. The launcher works out the source root from the launch file's package declaration (a file declaring package org.zoo at app/org/zoo/Keeper.java has the root app), and when the program first refers to another class it finds and compiles that class's source file from the root on demand.
// Greeter.java
public class Greeter {
public static void main(String[] args) {
System.out.println(Names.shout("ada"));
}
}
// Names.java
class Names {
static String shout(String s) {
return s.toUpperCase() + "!";
}
}With both files in one directory, java Greeter.java prints ADA! and leaves the directory unchanged: still no class files. Because compilation is on demand, a source file the program never references is not compiled, so a type error in an unused Scratch.java does not stop the program (javac *.java would report it).
Libraries
Compiled libraries are supplied with --class-path (or -cp) placed before the file name: java --class-path lib/feed.jar app/org/zoo/Keeper.java. Put the option after the file and it becomes an argument to main, so compilation fails with "package ... does not exist". The java launcher has no --source-path option; that belongs to javac.
Compiling with javac and running with java -cp
javac -d out writes each class into a directory tree that mirrors its package under out, so a class declaring package org.zoo.feed lands at out/org/zoo/feed/Menu.class regardless of where its source file lives. Without -d, javac writes the class file next to its source.
To compile a class whose dependencies have not been compiled yet, list every source file, or point javac at the source root with --source-path (old spelling -sourcepath) so it finds and compiles them itself. When no source path is given, javac also searches the class path for source files. Either path must name the package root (src), not a directory inside it such as src/org/zoo.
javac -d out --source-path src src/org/zoo/Keeper.java
java -cp out org.zoo.KeeperRunning needs two things that match: the class path points at the package root, and the class is named by its fully qualified name. The two classic failures:
| Command | Result | Why |
|---|---|---|
java -cp out org.zoo.Keeper | Runs | Root plus fully qualified name |
java -cp out Keeper | ClassNotFoundException: Keeper | There is no out/Keeper.class |
java -cp out/org/zoo Keeper | NoClassDefFoundError: Keeper (wrong name: org/zoo/Keeper) | The file is found, but it declares a different, packaged name |
On Linux and macOS the class path separator is :; on Windows it is ;.
Non-modular JARs, the manifest and java -jar
A non-modular JAR is a ZIP of class files plus META-INF/MANIFEST.MF; to be runnable with java -jar, its manifest needs a Main-Class attribute, which jar --main-class (short form -e) writes for you.
jar --create --file keeper.jar --main-class org.zoo.Keeper -C out .
java -jar keeper.jar-C out . changes into out and adds everything in it, so package directories sit at the JAR root. Without --main-class (or a manifest that sets Main-Class), java -jar tools.jar stops with "no main manifest attribute, in tools.jar"; the launcher never searches the JAR for a main method.
Where dependencies come from
With -jar, the JAR is the whole user class path: -cp and CLASSPATH are ignored. Dependencies must be listed in the manifest's Class-Path attribute (space-separated URLs relative to the JAR's location), which you can supply with jar --manifest:
# cp.mf contains the line: Class-Path: lib/feed.jar
jar --create --file keeper.jar --main-class org.zoo.Keeper \
--manifest cp.mf -C out .| Command | Dependency found? |
|---|---|
java -jar keeper.jar with Class-Path: lib/feed.jar | Yes |
java -cp lib/feed.jar -jar plain.jar (no Class-Path) | No: -cp is ignored, so Keeper starts and then throws NoClassDefFoundError: org/zoo/feed/Menu |
java -cp plain.jar:lib/feed.jar org.zoo.Keeper | Yes: no -jar, so the class path and class name are used |
java -jar plain.jar -cp lib/feed.jar | No: words after the JAR are arguments to main |
Modular JARs and running from the module path
A modular JAR is a JAR with a compiled module-info.class at its root; placed on the module path (-p / --module-path) it is loaded as the named module it declares. Building it with --main-class records a ModuleMainClass attribute in the descriptor, and jar --describe-module --file prints the descriptor back:
jar --create --file mods/app.jar \
--main-class org.zoo.app.Start -C classes/org.zoo.app .
jar --describe-module --file mods/app.jar
java -p mods -m org.zoo.app
java --module-path mods --module org.zoo.app/org.zoo.app.StartThe last two lines are the same launch in short and long form: -p is short for --module-path and -m is short for --module.
-m module(or--module module) works only when the descriptor has aModuleMainClass; otherwise the launcher reports that the module does not have one.-m module/classalways works.-mtakes a module name:-m org.zoo.app.Startfails with "Module org.zoo.app.Start not found", and-m org.zoo.app org.zoo.app.Startpasses the class name tomainas an argument.- Without
-m, the main class always comes from the class path, even when-pis given.java -p mods org.zoo.app.Startsearches the class path, which defaults to the current directory, so it fails with "Could not find or load main class org.zoo.app.Start" caused byClassNotFoundException. The module path alone never supplies a main class. - The module path must hold every required module:
-p mods/app.jaralone fails withFindException: Module org.zoo.core not found, required by org.zoo.app. -jarignores the module system for the JAR itself:java -p mods -jar mods/app.jarruns app.jar from the class path, and nothing adds the required module, so the first use of it throwsNoClassDefFoundError.
Where a JAR goes decides what it is
The same modular JAR placed on the class path is just a bag of classes in the unnamed module: its module-info.class is ignored, and Start.class.getModule().getName() returns null. Verified on JDK 25, java -cp mods/app.jar:mods/core.jar org.zoo.app.Start runs and prints null for the module name, while the -p mods -m commands print org.zoo.app.
Module path order
When the same module name appears in more than one module path entry, the first entry wins; versions are never compared. With org.zoo.core 2.0 in old and 3.0 in next, -p mods:old:next runs the older 2.0 build simply because old is listed first. Two JARs for the same module inside one directory are an error instead (FindException: Two versions of module ... found).
Runtime images with jlink
jlink assembles a custom runtime image: a self-contained directory with its own bin/java that holds only the modules an application needs. You name the root modules with --add-modules; jlink resolves their requires transitively from --module-path (the JDK's own modules are found automatically) and links exactly that set.
jlink --module-path mods --add-modules org.zoo.app \
--launcher zoo=org.zoo.app --output zooimg
zooimg/bin/zoo
zooimg/bin/java --list-modulesFor an app that requires only org.zoo.core and java.base, --list-modules shows exactly java.base, org.zoo.app and org.zoo.core, not the full JDK.
| Option | Effect |
|---|---|
--module-path | Where your modules (modular JARs, exploded modules, JMODs) are found |
--add-modules | Root modules; their dependencies come along transitively |
--launcher name=module[/class] | Writes bin/name, which starts that module's main class. Without /class the module needs a ModuleMainClass |
--output | The image directory; it must not exist yet ("directory already exists") |
--strip-debug, --no-header-files, --no-man-pages | Make the image smaller |
- A launcher script already knows its main class, so everything typed after
zooimg/bin/zoogoes tomainas arguments. The image's ownzooimg/bin/java -m org.zoo.appalso works, because the module is inside the image. - An image is not a module path:
java -p zooimg -m org.zoo.appfrom the JDK fails withFindException. - jlink links named, explicit modules only. If anything in the graph is an automatic module, it stops with "automatic module cannot be used with jlink", even though
java -p mods -m ...runs the same graph fine. Class-path JARs cannot be linked at all.
The unnamed module and automatic modules
Migration rests on two bridge modules. Everything on the class path belongs to the unnamed module; a plain JAR (no module-info.class) on the module path becomes an automatic module.
| Named (explicit) module | Automatic module | Unnamed module | |
|---|---|---|---|
| Comes from | JAR with module-info.class on the module path | Plain JAR on the module path | Anything on the class path |
| Name | From its descriptor | Derived (see below) | None (getName() is null) |
| Exports and opens | Only what it declares | Every package, exported and open | Every package |
| Reads | Only what it requires | Every module, including the unnamed module | Every module in the graph |
| Can be required by a named module? | Yes | Yes, by its name | No |
| Can be linked by jlink? | Yes | No | No |
Unnamed module rules
- A named module cannot read the unnamed module, so it cannot use class-path packages: compiling
module org.zoo.shopagainst a class-path JAR fails with "package net.oldlog is not visible (package net.oldlog is declared in the unnamed module, but module org.zoo.shop does not read it)". - When the main class is on the class path, modules on the module path are not resolved by default. Add them with
--add-modules name(orALL-MODULE-PATH); without it, the first use of a type from that module throwsNoClassDefFoundError. - Reading every module does not bypass encapsulation: class-path code that calls a public class in a non-exported package of a named module compiles against the JAR on the class path, but at run time throws
IllegalAccessError.
How automatic modules are named
If the manifest has an Automatic-Module-Name attribute, that is the name, and the file name no longer matters. Otherwise the name is derived from the file name: drop .jar; drop the version, which starts at a hyphen followed by one or more digits and then a dot or the end of the name (so -2.17.1 or -4 is a version, but -2d is not); then replace every run of non-alphanumeric characters with a single dot and trim leading or trailing dots.
| JAR file | Automatic module name |
|---|---|
jackson-core-2.17.1.jar | jackson.core (version 2.17.1) |
fast_csv-4.0.jar | fast.csv (version 4.0) |
old.tools-cli-5.2-RC1.jar | old.tools.cli (version 5.2-RC1) |
zoo-map3d.jar | zoo.map3d (the digit does not follow a hyphen, so no version) |
lib-2d.jar | None usable: -2d is not a version, so the name derives as lib.2d, which jar --describe-module rejects ("'2d' is not a Java identifier") |
A derived name that is not a legal module name (a segment that starts with a digit, as above, or one that is a Java keyword) makes the JAR unusable on the module path until it gets an Automatic-Module-Name.
Why automatic modules bridge the two worlds
Because an automatic module reads the unnamed module, it can call class-path JARs that a named module cannot. And because it exports and opens everything, a named module that requires it can use all its public types and even reflect on its private members with setAccessible(true). Readability also spreads: a module that reads one automatic module reads all automatic modules in the graph, so code that requires only alpha can still compile and run against classes in the automatic module beta. That works, but it is an accident to remove, not a design to rely on.
Migration strategies and split packages
Migrating to modules means adding a module-info.java to one JAR at a time while the application keeps running; the order is either bottom-up or top-down.
| Bottom-up | Top-down | |
|---|---|---|
| Converted first | The JARs with no dependencies (the leaves), then the ones that use them, the application last | The application JAR first |
| Not yet converted code | Stays on the class path, in the unnamed module, and can still use the new named modules | Libraries move unchanged to the module path, where they become automatic modules the new modules requires by name |
| Why this order | A new named module may only require modules that are already named or automatic; it can never read the class path | You don't control (or can't wait for) third-party libraries |
| jlink | Possible once the whole graph is explicit | Not until every automatic module has been replaced by an explicit one |
For a chain where web.jar calls orders.jar and orders.jar calls db.jar, bottom-up converts db, then orders, then web. Libraries that stay on the class path during a top-down migration can still be reached through an automatic module, because automatic modules read the unnamed module: java -p mods -cp lib/oldlog.jar -m org.zoo.web lets org.zoo.web require the automatic module csvkit (from csvkit-2.0.jar), which in turn calls classes in the class-path JAR oldlog.jar. Leave the -cp off and the call from csvkit throws NoClassDefFoundError. The jdeps tool helps plan either order by listing each JAR's dependencies.
Split packages
A package may belong to only one module that a given module reads. Two JARs on the class path may happily share a package, but put both on the module path and resolution fails before main runs:
java.lang.module.ResolutionException: Module report.extra
contains package net.report, module report exports
package net.report to report.extraHere report.jar and report-extra.jar both contain classes in net.report. The conflict is about the package, not the module names, so giving either JAR a different module name changes only the names in the message (automatic modules read each other, so the message names the two JARs, not org.zoo.web), and adding root modules changes nothing. The fixes are to merge the package's classes into one JAR, or to keep only one copy on the module path and move the other JAR to the class path (the unnamed module's copy of the package is then simply not visible to the named modules that read the module-path copy).
Tip. Objective 7.2 is examined mostly through exact commands and short programs: what a given java, javac, jar or jlink command line does, which of several commands works, or what a compact source file or a class with several main methods prints. Expect launcher error messages and exceptions (ClassNotFoundException, NoClassDefFoundError, FindException, ResolutionException, IllegalAccessError) as genuine answers alongside outputs, and multiple-answer items asking which two commands run a program. Distractors lean on near-miss details: an option placed after the file or JAR name, a class path that points inside the package tree, -m given a class name, a JAR on the class path rather than the module path, a file name that derives a different automatic module name, or a jlink run over a graph that still contains an automatic module. Migration items ask about ordering, what the unnamed and automatic modules can read, and why split packages fail.
- A compact source file is an implicitly declared final class in the unnamed package: default constructor only, no package declaration, a required main method, and no code can refer to it by name.
- Compact source files import all of java.base automatically; java.lang.IO is usable from every class without an import, but an ordinary class must still import java.util.List and friends.
- The launcher prefers a non-private void main(String[]) (declared or inherited) over void main(); private methods are never candidates; an instance main needs a non-private no-arg constructor, which runs (with field initialisers) first.
- java File.java compiles in memory, writes no class files, launches the first top-level class in the file, and passes everything after the file name to main; launcher options go before it.
- Multi-file source programs compile other source files on demand from the root implied by the package; unreferenced files are not compiled; libraries go in --class-path before the file name.
- javac -d places classes by package; java -cp needs the package root plus the fully qualified class name, or you get ClassNotFoundException or a wrong-name NoClassDefFoundError.
- java -jar needs a Main-Class manifest attribute and ignores -cp entirely; dependencies come from the manifest Class-Path.
- A modular JAR is a named module only on the module path; -m module needs ModuleMainClass (jar --main-class), -m module/class always works, and the first module-path entry with a given name wins.
- jlink links the root modules plus their transitive requires into an image whose --output must not exist; it refuses automatic modules.
- Named modules cannot read the unnamed module; automatic modules export and open everything and read everything, which is why they bridge migration; split packages on the module path fail resolution.
Frequently asked questions
What is a compact source file in Java 25?
A compact source file is a .java file whose fields and methods are written without an enclosing class declaration. The compiler wraps them in an implicitly declared final class in the unnamed package, with only a default constructor, that no other code can refer to by name. The file must contain a launchable main method, and it automatically imports every package exported by java.base. Compact source files became a standard feature in Java 25 (JEP 512).
Which main method does Java 25 run when a class has more than one?
The launcher first looks for a non-private void main(String[] args), including one inherited from a superclass; only if there is none does it use a non-private void main() with no parameters. Private methods are ignored. If the chosen method is static it is called directly; if it is an instance method, the launcher first creates an object with the non-private no-argument constructor, which also runs the field initialisers.
Does java File.java create class files?
No. In source-file mode the java launcher compiles the source in memory and runs it without writing any .class files. It launches the first top-level class declared in the file, and every word after the file name is passed to main as an argument. Since Java 22 the program may span several source files, which are compiled on demand from the source root implied by the launch file's package.
Why does java -jar ignore my -cp option?
When you run java -jar app.jar, the JAR is the entire user class path, so the -cp option and the CLASSPATH environment variable are ignored. Dependencies must be listed in the JAR manifest's Class-Path attribute as paths relative to the JAR's location. Alternatively, drop -jar and run java -cp app.jar:lib/dep.jar with the main class given by its fully qualified name.
How is an automatic module's name chosen?
If the plain JAR's manifest has an Automatic-Module-Name attribute, that value is the module name. Otherwise the name is derived from the file name: remove .jar, remove the version, which starts at a hyphen followed by digits and then a dot or the end of the name, replace each run of non-alphanumeric characters with a dot, and trim leading and trailing dots. For example, jackson-core-2.17.1.jar becomes jackson.core, while lib-2d.jar derives lib.2d, which is not a valid module name.
Can jlink include an automatic module or a class-path JAR?
No. jlink links only explicit named modules, so if any module in the resolved graph is an automatic module it stops with the error that an automatic module cannot be used with jlink, and class-path JARs cannot be added at all. The application can still run with java on the module path; building an image requires replacing each automatic module with a JAR that has a module-info.class.
What is the difference between bottom-up and top-down migration to modules?
Bottom-up migration converts the libraries with no dependencies first and the application last, leaving unconverted JARs on the class path where they can still use the new named modules. Top-down migration converts the application first and moves unchanged libraries to the module path as automatic modules that the application requires by name. Top-down suits third-party libraries you cannot change; bottom-up works when you own the whole dependency chain.
What is a split package and how do you fix it?
A split package is one package present in two modules that the same module reads, for example two plain JARs that both contain the package net.report placed on the module path. Module resolution fails with a ResolutionException before main runs, although the same JARs work on the class path. Fix it by merging the package into one JAR, or by keeping one JAR on the module path and moving the other to the class path; renaming the JAR or the automatic module does not help.
Source
This lesson covers the "Packaging and Deploying Java Code" domain of the official 1Z0-831 exam guide. Vendors revise their guides — check the source for the current version.
- Java SE 25 Developer Professional (1Z0-831) exam page and topics — Oracle University
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.