skip to content
everything.oop.packagesAndImports

packages and imports

fully qualified names, the classpath, and import module.

chapter
chapter 03.05
reading time
6 min read

a package is a namespace, and every class's real name includes it: java.util.ArrayList, not ArrayList. that's the fully qualified name. an import just lets you write the short name. it loads nothing and costs nothing at runtime, it only exists while javac is reading your file.

java 25 adds import module, which imports every package a module exports in one line.

the picture

think of a postal address. "ada" isn't enough to find someone. "ada, 12 elm street, london" is. a package is the street and city (the namespace), the class name is the person, and an import is you telling the mail carrier "when i say ada, i mean the one on elm street".

packages also map to folders. package com.acme.shop; at the top of Cart.java means the source lives in com/acme/shop/, and the compiled class lands in com/acme/shop/Cart.class.

  • ├──
  • │ └──
  • │ ├── package com.acme.shop
  • │ │ ├── Cart.javacom.acme.shop.Cart
  • │ │ ├── Item.javacom.acme.shop.Item
  • │ │ └── package com.acme.shop.checkout (a different package)
  • │ │ └── Payment.javacom.acme.shop.checkout.Payment
  • │ └── package com.acme.util
  • │ └── Money.javacom.acme.util.Money
  • └── Main.javano package line: the unnamed package
folders on the left, the names they give classes on the right. the dots in a package name are just slashes in disguise. click a folder to fold it.

a few things that tree says that people get wrong:

  • com.acme.shop.checkout is not inside com.acme.shop as far as java cares. the names nest, the packages don't. there's no access between them and import com.acme.shop.* doesn't reach into checkout.
  • the reverse-domain style (com.acme) is a convention for avoiding clashes between libraries, nothing more.
  • a class with no package line lives in the unnamed package. fine for a quick Main.java, but classes in a named package can't import it at all.

how a class is found at runtime

your bytecode never contains ArrayList. it always contains java/util/ArrayList. when the JVM needs a class, a class loader turns the name into a path and looks for it.

hover a stage.

hover each stage. for your own classes the search goes through the classpath entries in order, and the first match wins. JDK classes like java.util.ArrayList are found earlier, by the boot and platform loaders, inside the JDK's modules.

"first match wins" is a real footgun. two jars with the same class, and the order on the command line decides which one runs:

terminal
$ java -cp a:b com.acme.Greeter
hello from a/
$ java -cp b:a com.acme.Greeter
hello from b/
$ java -cp . com.acme.Greeter
Error: Could not find or load main class com.acme.Greeter
Caused by: java.lang.ClassNotFoundException: com.acme.Greeter

that's the classpath in one picture. the details (and why modules were invented to stop this) are in class loaders and classpath to module path.

the four kinds of import

Imports.java
import java.util.List;           // single-type: exactly one class
import java.util.*;              // on-demand: any class in java.util, when you use it
import static java.lang.Math.max; // static: a static member, used without the class name
import module java.sql;          // module (java 25): every package java.sql exports

and one you never write: every file gets import java.lang.*; for free. that's why String, Math and System just work.

St.java
import static java.lang.Math.max;
import static java.lang.Math.PI;
 
public class St {
    public static void main(String[] args) {
        System.out.println(max(3, 7) + " " + PI);
    }
}
output
7 3.141592653589793

import module java 25+

import module M; imports, on demand, every public class in every package M exports, plus the packages of modules M requires transitively (JEP 511, final in java 25). import module java.base; alone covers all 58 packages java.base exports on jdk 25: java.util, java.io, java.time, java.util.function, java.util.stream, java.nio.file and the rest.

compact source files get that one implicitly. so this whole program has no imports and still uses List, ArrayList, Map and HashMap:

Main.java
void main() {
    List<String> names = new ArrayList<>(List.of("ada", "duke"));
    Map<String, Integer> lengths = new HashMap<>();
    for (String n : names) lengths.put(n, n.length());
    IO.println(names + " " + lengths.get("ada"));
}
output
[ada, duke] 3

when two classes share a name

java.util.Date and java.sql.Date both exist. import both packages with wildcards and javac can't pick. the rule for who wins is a ranking: single-type imports beat on-demand ones, and on-demand ones beat module imports. two at the same rank: ambiguous.

try =
Main.javaclick a line to toggle it
7
8public class Main {
9 public static void main(String[] args) {
10 Date d = new Date(0);
11 }
12}
error: reference to Date is ambiguous

two on-demand imports both supply a Date, and neither is more specific. so it's ambiguous, but only because the code actually uses Date.

toggle import lines, or pick a preset. the result is what javac 25 actually did with that exact file: every one of the 64 combinations was compiled. green is the class you got, red is the error.
quick check
which of these does import java.util.* let you use without another import?
one level deeper:imports really do vanish+

compile a class that does new ArrayList<String>() with import java.util.ArrayList;, and run javap -c on it:

text
0: new           #7      // class java/util/ArrayList
4: invokespecial #9      // Method java/util/ArrayList."<init>":()V
11: invokevirtual #12    // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z

no import anywhere, only full names. so import java.util.* versus a list of single-type imports makes zero difference to the class file, startup or speed. the choice is purely about readability and clash-safety.

keylines
  • a class's real name is its fully qualified name: package + class.
  • packages map to folders, and they don't nest. a.b.c has nothing to do with a.b.
  • imports are a compile-time shortcut. bytecode only has full names.
  • at runtime the classpath is searched in order, and the first match wins.
  • single-type beats on-demand beats module. ties are ambiguous, but only when used.