Packages, directories and where classes go

Why a packaged class must be run by its full name, and what -d is for.

3 min read🔧 Setting Up Java

A package is a namespace. It exists so that two libraries can both have a class called Order without colliding, and so that a reader can tell which List you meant. It also imposes a rule about directories that catches everybody exactly once.

The rule

A class declares its package on the first line:

java
package com.shop;
 
public class Order { … }

Its full name is now com.shop.Order, and that is its real name — Order is shorthand that only works inside the same package or after an import.

The compiler expects the directory structure to mirror the package:

plaintext
com/shop/Order.java       package com.shop;

And the JVM expects the same of the compiled output. That is what produces the failure people meet on their first packaged class:

plaintext
$ javac com/shop/Order.java
$ java Order
Error: Could not find or load main class Order
 
$ java com.shop.Order
ok

The short name does not work. You run a class by its full name, because the full name is the name.

-d, and what a build tool does

Compiling in place mixes source and output. -d separates them, and recreates the package directories underneath:

plaintext
$ javac -d out com/shop/Order.java
$ find out -name "*.class"
out/com/shop/Order.class

Then out is the root the JVM should search — the classpath:

bash
java -cp out com.shop.Order

That is the whole mechanism, and every build tool is doing exactly this with more ceremony: sources under src/main/java, classes under target/classes or build/classes, and a classpath assembled from those plus every dependency.

Naming, and the convention that is not arbitrary

Packages are lower case and read as reversed domain names:

plaintext
com.shop.billing
org.springframework.boot
java.util

Reversing a domain guarantees uniqueness without a registry: nobody else can legitimately publish under com.yourcompany. That convention is why every library you import looks like this, and why com.example appears in every tutorial.

Two rules worth having:

  • Never use the default package — a class with no package line. It cannot be imported by anything in a package, which makes it unusable from real code. Tutorials use it for brevity; nothing else should.
  • Group by feature, not by layer. com.shop.billing containing its controller, service and repository beats com.shop.controllers containing thirty unrelated controllers. Things that change together should live together, and a feature changes together.

The layout you will see everywhere

plaintext
project/
├── pom.xml  or  build.gradle        the build's configuration
└── src/
    ├── main/
    │   ├── java/                    your code, in package directories
    │   │   └── com/shop/Order.java
    │   └── resources/               config, templates, static files
    └── test/
        ├── java/                    tests, mirroring main's packages
        └── resources/

This is Maven's convention, which Gradle adopted, which everything else now assumes. Nothing enforces it beyond the tools defaulting to it — and that is enough, because a new engineer opening your project knows where to look without asking.

src/test/java mirroring src/main/java matters more than it looks: a test for com.shop.Order in package com.shop can reach that class's package-private members, which is how you test something without making it public just for the test.

Misconceptions

  • "The package is just a folder." The folder mirrors it. The package is part of the class's identity — two classes with the same simple name in different packages are entirely different types.
  • "Imports load the class." An import is a compile-time shorthand so you can write Order instead of com.shop.Order. Nothing is loaded because of it; loading happens when the class is first used.
  • "import com.shop.* is slower." It is not; it makes no run-time difference. It is discouraged because it hides which class you meant, and two wildcard imports can collide.
Progress is saved on this device and to your account when signed in.