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
a few things that tree says that people get wrong:
com.acme.shop.checkoutis not insidecom.acme.shopas far as java cares. the names nest, the packages don't. there's no access between them andimport com.acme.shop.*doesn't reach intocheckout.- the reverse-domain style (
com.acme) is a convention for avoiding clashes between libraries, nothing more. - a class with no
packageline lives in the unnamed package. fine for a quickMain.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.
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:
$ 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.Greeterthat'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
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 exportsand one you never write: every file gets import java.lang.*; for free. that's why String, Math and System just work.
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);
}
}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:
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"));
}[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.
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.
imports really do vanish+
compile a class that does new ArrayList<String>() with import java.util.ArrayList;, and run javap -c on it:
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;)Zno 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.
- a class's real name is its fully qualified name: package + class.
- packages map to folders, and they don't nest.
a.b.chas nothing to do witha.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.