Command Palette

Search for a command to run...

PHASE 4Beginner ~27 min· topic 7 of 12

Topic 4.7

Packages and Imports

In one line

A package is a named group of related classes, stored in a matching folder, that gives every class a unique full name such as java.util.ArrayList. import lets you write the short name instead, and package-private access lets classes in one package share helpers without exposing them.

Think of it like this

A big school has two pupils called Riya. To avoid mix-ups, the register says "Riya, class 5B" and "Riya, class 7A". Inside 5B everyone just says "Riya". A package is the class name like "5B": it makes the full name unique. An import is like saying at the start of a conversation "when I say Riya I mean Riya from 7A".

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Package
A named group of related classes, like a folder with a unique name. It prevents name clashes and controls access.
Fully qualified name
A class's complete name including its package, like java.util.ArrayList. It's unique.
Simple name
Just the class's own name, like ArrayList, without the package.
`import`
A line at the top of a file that lets you use a class by its simple name. It doesn't load anything; it's a naming shortcut for the compiler.
On-demand import
An import ending in .* that makes all public types of one package usable by simple name.
Static import
An import that brings in static fields or methods so you can use them without the class name, e.g. PI instead of Math.PI.
Unnamed package
The package a class is in when its file has no package statement. Also called the default package.
Classpath
The list of folders and JAR files where the JVM and javac look for .class files. Set with -cp.

Step by step

01Packages are names and folders

A small shop app might have a model package for data classes and an app package for the entry point. The package line names the package, and the folder path mirrors it.

The compiler enforces the output layout: javac -d out writes out/com/hectal/shop/model/Product.class no matter where the source file sat. The JVM finds classes by turning dots into folders under each classpath entry.

project layoutwhole filetext
src/
  com/hectal/shop/model/Product.java   ->  package com.hectal.shop.model;
  com/hectal/shop/app/Main.java        ->  package com.hectal.shop.app;
out/                                    (created by javac -d out)
  com/hectal/shop/model/Product.class
  com/hectal/shop/app/Main.class

02Write two classes in two packages

Product is public, as are its constructor and accessor methods, so other packages can use them. internalCode() has no modifier, so it's package-private: visible to com.hectal.shop.model only.

Main imports Product by its full name once, then uses the short name.

src/com/hectal/shop/model/Product.javawhole filejava
package com.hectal.shop.model;

public class Product {
    private final String name;
    private final long pricePaise;

    public Product(String name, long pricePaise) {
        this.name = name;
        this.pricePaise = pricePaise;
    }

    public String name() { return name; }
    public long pricePaise() { return pricePaise; }

    String internalCode() { return "P-" + name.toUpperCase(); }   // package-private
}

03Compile to an output folder and run by full name

-d out tells javac where to put class files. To run, put out on the classpath and give the fully qualified name of the class with main.

If you cd into out/com/hectal/shop/app and type java Main, it fails: the class file says its name is com.hectal.shop.app.Main, not Main. Always run from the classpath root.

src/com/hectal/shop/app/Main.javawhole filejava
package com.hectal.shop.app;

import com.hectal.shop.model.Product;

public class Main {
    public static void main(String[] args) {
        Product p = new Product("tea", 2000);
        System.out.println(p.name() + " costs " + p.pricePaise() + " paise");
    }
}
terminal
$ javac -d out src/com/hectal/shop/model/Product.java src/com/hectal/shop/app/Main.java
java -cp out com.hectal.shop.app.Main
── expected output ──
tea costs 2000 paise

04Package-private: shared inside, hidden outside

Calling p.internalCode() from Main fails, because Main is in a different package. Any class inside com.hectal.shop.model could call it.

This is how you build a package with a small public surface and private helpers: only public things are the package's API. Topic 4.5 introduced the four access levels; packages are where package-private earns its keep.

terminal
$ javac -d out src/com/hectal/shop/model/Product.java src/com/hectal/shop/app/Main.java
── expected output ──
src/com/hectal/shop/app/Main.java:8: error: internalCode() is not public in Product; cannot be accessed from outside package
System.out.println(p.internalCode());
^
1 error

05How the compiler resolves a simple name

When javac sees Product, it looks in this order: a type declared in the current file or a single-type import; then types in the same package; then on-demand imports (.*, including the automatic java.lang.*). If two on-demand imports both supply the name, it's ambiguous.

Imports happen entirely at compile time. The .class file contains only fully qualified names, so import java.util.* has zero run-time cost and doesn't make the program bigger or slower. Style guides still prefer explicit imports because they show exactly where each name comes from.

How the compiler resolves a simple namediagram
Rendering diagram…

06Name clashes and how to settle them

java.util.Date and java.sql.Date, or java.util.List and java.awt.List: the JDK itself has duplicates. With import java.util.*; and import java.awt.*;, using List is an error.

Fix: add import java.util.List;. A single-type import beats on-demand imports. If you need both in one file, import one and write the other's full name: java.awt.List awtList = ....

terminal
$ javac Main.java
── expected output ──
Main.java:5: error: reference to List is ambiguous
List xs = null;
^
both class java.awt.List in java.awt and interface java.util.List in java.util match
1 error

Try it yourself

  1. 1

    Remove an import

    In "Three ways to name a class", delete import java.util.*;. Predict which lines fail and the exact error (cannot find symbol ... class Map). Put it back.

  2. 2

    Prove .* is not recursive

    In "Every class has a full name", replace java.util.function.Function.class with Function.class and add import java.util.*;. Predict whether it compiles. Then add import java.util.function.Function;.

  3. 3

    Build a two-package project locally

    Create the Product/Main layout from the walkthrough, compile with -d out, and run with java -cp out com.hectal.shop.app.Main. Then try cd out/com/hectal/shop/app and java Main, and read the error.

    terminal
    $ cd out/com/hectal/shop/app
    java Main
    ── expected output ──
    Error: Could not find or load main class Main
    Caused by: java.lang.NoClassDefFoundError: Main (wrong name: com/hectal/shop/app/Main)

Code & diagrams

Three ways to name a class New tab
Sign in to run this example in your browser.

Expected output

[x] [y] {k=1}
max = 9
PI from java.lang: 3.141592653589793
Every class has a full name New tab

Main and Helper have no package statement, so they're in the unnamed package (an empty name).

Sign in to run this example in your browser.

Expected output

java.lang.String
java.util.ArrayList
java.util.function.Function
package of ArrayList: java.util
package of Main: []
Helper
Settle a clash with a fully qualified name New tab

java.sql.Date extends java.util.Date. For new code prefer java.time.LocalDate (Phase 10).

Sign in to run this example in your browser.

Expected output

java.util.Date
java.sql.Date
sql.Date is a util.Date? true
Static imports in tests (common real-world use)java

A fragment that needs JUnit 5 on the classpath; it shows where static imports read best.

import static org.junit.jupiter.api.Assertions.assertEquals;
import static java.util.concurrent.TimeUnit.SECONDS;

class PriceTest {
    @org.junit.jupiter.api.Test
    void addsTax() {
        assertEquals(118, Price.withGst(100));   // instead of Assertions.assertEquals
        long timeout = SECONDS.toMillis(5);
    }
}

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Use a class without importing it

Delete import java.util.ArrayList; and keep ArrayList<String> xs = new ArrayList<>();.

terminal
$ javac Main.java
── what you'll see ──
Main.java:3: error: cannot find symbol
ArrayList<String> xs = new ArrayList<>();
^
symbol: class ArrayList
location: class Main
Main.java:3: error: cannot find symbol
ArrayList<String> xs = new ArrayList<>();
^
symbol: class ArrayList
location: class Main
2 errors

Break #2

Two on-demand imports with the same name

Write import java.util.*; and import java.awt.*;, then declare List xs = null;.

terminal
$ javac Main.java
── what you'll see ──
Main.java:5: error: reference to List is ambiguous
List xs = null;
^
both class java.awt.List in java.awt and interface java.util.List in java.util match
1 error

Myth vs fact

Myth

import java.util.* makes the program slower or bigger.

Fact

Imports exist only at compile time. The class file stores fully qualified names either way; there's no run-time cost.

Myth

import java.util.* also imports java.util.function and java.util.concurrent.

Fact

Packages aren't nested for Java's purposes. java.util.function is a separate package that needs its own import.

Myth

import loads or includes the class, like #include in C.

Fact

It only tells the compiler how to expand a short name. Classes are loaded lazily by the JVM when first used, whether imported or written in full.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Packages don't form a hierarchy for access: com.hectal.shop and com.hectal.shop.model are unrelated, and package-private members of one aren't visible in the other. The dots only mirror folders.

  • ▸

    Modules (Java 9, Phase 15) add exports: a public class in a non-exported package is unreachable from other modules. Two modules containing the same package (a split package) are illegal on the module path, which is a common migration headache.

  • ▸

    Java 25 finalised module import declarations (JEP 511): import module java.base; imports every exported package of a module on demand. It's handy for scripts and teaching; ambiguity rules still apply (single-type imports win).

  • ▸

    Classes in the unnamed package can't be imported by classes in named packages (since Java 1.4). Real projects always use named packages, and tools like Maven and Gradle expect src/main/java/<package folders>.

Remember this

  1. 1

    A package is declared with package com.hectal.shop.model; as the first statement of a file (only comments may come before it). The class's fully qualified name becomes com.hectal.shop.model.Product. Two libraries can both have a class named Product as long as their packages differ.

  2. 2

    By convention package names are lower-case and start with a reversed internet domain you control: in.hectal.shop, com.google.common. The source file must sit in a matching folder path (com/hectal/shop/model/Product.java), and the compiled .class file always lands in that path under the output folder.

  3. 3

    An import only saves typing; it doesn't load or copy anything. import java.util.ArrayList; (single-type import) lets you write ArrayList. import java.util.*; (on-demand import) makes every public type in java.util available by simple name, but not sub-packages: java.util.* does not include java.util.function.Function.

  4. 4

    java.lang is imported automatically, which is why String, Math, System and Integer need no import. Classes in the same package need no import either. A file with no package line is in the unnamed (default) package, fine for small experiments like this course's examples, but classes in named packages can't import it.

  5. 5

    A static import (Java 5) brings static members in by simple name: import static java.lang.Math.max; lets you write max(a, b). Use it sparingly, for well-known constants and methods; too many make it unclear where a name came from.

  6. 6

    When two imports bring in the same simple name (java.util.List and java.awt.List), using List is a compile error: reference to List is ambiguous. Fix it with a single-type import (which beats an on-demand one) or by writing the fully qualified name in place.

Explain it without notes

01

What problems do packages solve?

02

What does import actually do, and what does it cost at run time?

03

Explain the order in which javac resolves a simple class name, and how an ambiguity arises.

04

Why does running java Main inside the class's own folder fail for a class in a package?

Practice

01

Write a program that uses java.util.List, java.util.Map and java.util.function.Function with explicit single-type imports: build a list of three words, map each to its length with a Function<String, Integer>, store the results in a TreeMap, and print it.

02

Use import static java.lang.Math.*; to print sqrt(144), abs(-7) and pow(2, 10).

03

Without any import statement, create a java.util.ArrayList<Integer>, add 3 and 4, and print its size and contents.

Trade-offs

  • ↔

    Explicit single-type imports document exactly where each name comes from and avoid future ambiguity when a library adds a class, at the cost of a longer import list (IDEs manage it). On-demand imports are shorter but can break the build when a new version of a package adds a clashing name.

  • ↔

    Fine-grained packages (one per feature) keep package-private helpers truly private, but too many tiny packages force you to make things public just to share them. Group by feature, not by layer, so related classes can stay package-private.

Done when you can

  • Done when you can write package lines and place files in the matching folders.

  • Done when you can compile with -d out and run with -cp out and a fully qualified name.

  • Done when you know what java.lang and the same package give you without imports.

  • Done when you can resolve an ambiguous name with a single-type import or a full name.

  • Done when you can explain why imports cost nothing at run time.