SaveMyCert
Log in
5 of 5 free questions left today·for 30 a day
Packaging and Deploying Java Code

Compiling and Running Java: Compact Source Files, JARs, jlink and Module Migration (1Z0-831)

20 min read1Z0-831 · Packaging and Deploying Java CodeUpdated

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.

What you’ll learn
  • 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

PropertyWhat it means in practice
final, extends ObjectNothing can subclass it; javap shows final class Main.
Unnamed packageA package declaration is a compile error ("compact source file should not have package declaration").
Only the default constructorWriting Main() { } is read as a method missing its return type, so it fails to compile.
Cannot be referenced by nameThe 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 mainA 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 usedCompact source fileOrdinary class declaration
IO, String, Math (java.lang)No import neededNo import needed
List, Path, other java.base packagesNo import neededNeeds import java.util.List; (or a module import)
Types outside java.base (e.g. java.sql)Needs an importNeeds 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:

  1. If the class declares or inherits a non-private void main(String[] args) (the varargs form String... counts), that method is chosen.
  2. Otherwise it looks for a non-private void main() with no parameters.
  3. 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 calls main on 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 main launches. If Main extends Base and only Base declares void main(), then java -cp out Main creates a Main and runs the inherited method, so getClass() inside it reports Main.
  • Inheritance does not change the order: an inherited static void main(String[]) still beats a void main() declared in the subclass itself.
  • If an instance main is chosen but the class has no no-argument constructor (only constructors with parameters), the launcher stops before main runs. 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, then java -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 A and then class 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 javac would fail if a public class'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.class sits beside an edited Main.java, java Main.java compiles and runs the edited source, while java Main runs whatever class file is on the class path.
CommandReadsWrites class files?
java Main.java a bSource, compiled in memoryNo
javac Main.java then java Main a bThe compiled Main.class on the class pathYes (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.Keeper

Running 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:

CommandResultWhy
java -cp out org.zoo.KeeperRunsRoot plus fully qualified name
java -cp out KeeperClassNotFoundException: KeeperThere is no out/Keeper.class
java -cp out/org/zoo KeeperNoClassDefFoundError: 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 .
CommandDependency found?
java -jar keeper.jar with Class-Path: lib/feed.jarYes
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.KeeperYes: no -jar, so the class path and class name are used
java -jar plain.jar -cp lib/feed.jarNo: 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.Start

The 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 a ModuleMainClass; otherwise the launcher reports that the module does not have one. -m module/class always works.
  • -m takes a module name: -m org.zoo.app.Start fails with "Module org.zoo.app.Start not found", and -m org.zoo.app org.zoo.app.Start passes the class name to main as an argument.
  • Without -m, the main class always comes from the class path, even when -p is given. java -p mods org.zoo.app.Start searches 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 by ClassNotFoundException. The module path alone never supplies a main class.
  • The module path must hold every required module: -p mods/app.jar alone fails with FindException: Module org.zoo.core not found, required by org.zoo.app.
  • -jar ignores the module system for the JAR itself: java -p mods -jar mods/app.jar runs app.jar from the class path, and nothing adds the required module, so the first use of it throws NoClassDefFoundError.

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).

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) moduleAutomatic moduleUnnamed module
Comes fromJAR with module-info.class on the module pathPlain JAR on the module pathAnything on the class path
NameFrom its descriptorDerived (see below)None (getName() is null)
Exports and opensOnly what it declaresEvery package, exported and openEvery package
ReadsOnly what it requiresEvery module, including the unnamed moduleEvery module in the graph
Can be required by a named module?YesYes, by its nameNo
Can be linked by jlink?YesNoNo

Unnamed module rules

  • A named module cannot read the unnamed module, so it cannot use class-path packages: compiling module org.zoo.shop against 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 (or ALL-MODULE-PATH); without it, the first use of a type from that module throws NoClassDefFoundError.
  • 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 fileAutomatic module name
jackson-core-2.17.1.jarjackson.core (version 2.17.1)
fast_csv-4.0.jarfast.csv (version 4.0)
old.tools-cli-5.2-RC1.jarold.tools.cli (version 5.2-RC1)
zoo-map3d.jarzoo.map3d (the digit does not follow a hyphen, so no version)
lib-2d.jarNone 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-upTop-down
Converted firstThe JARs with no dependencies (the leaves), then the ones that use them, the application lastThe application JAR first
Not yet converted codeStays on the class path, in the unnamed module, and can still use the new named modulesLibraries move unchanged to the module path, where they become automatic modules the new modules requires by name
Why this orderA new named module may only require modules that are already named or automatic; it can never read the class pathYou don't control (or can't wait for) third-party libraries
jlinkPossible once the whole graph is explicitNot 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.extra

Here 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.

Key takeaways
  • 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.

Test yourself on this topic
Practice now

Sign up free to mark lessons complete, bookmark topics and track your exam readiness.

Spotted a mistake in this lesson?