Packages

July 2, 20264 min readUpdated 8/20/2026

A package is a namespace. It groups related classes, prevents name collisions between unrelated libraries, and — through access modifiers — decides who can see what. Two classes called Order can coexist happily as long as they live in different packages.

Declaring a package

package com.lovemesomecoding.orders;

public class Order {
    private final String id;

    public Order(String id) {
        this.id = id;
    }

    public String getId() { return id; }
}

The package line must be the first statement in the file, before any import. The package name maps directly onto directories, and the compiler enforces it:

src/main/java/
  com/
    lovemesomecoding/
      orders/
        Order.java          package com.lovemesomecoding.orders;
        OrderService.java
      payments/
        PaymentGateway.java package com.lovemesomecoding.payments;

The convention is your domain name reversed — com.lovemesomecoding.orders — which is what guarantees global uniqueness. Package names are always lowercase, and by convention singular where it reads naturally.

A class with no package line lands in the default package. This works for a one-file experiment and nothing else: classes in the default package cannot be imported from a named package, so anything real will be unable to use them.

Imports

import java.util.List;
import java.util.ArrayList;
import java.util.Map;

import static java.util.Map.entry;              // static import: the member itself

class Demo {
    void run() {
        List<String> names = new ArrayList<>();
        Map<String, Integer> ages = Map.ofEntries(entry("Folau", 30));
        System.out.println(names.size() + ages.size());
    }
}

An import is purely a convenience — it lets you write List instead of java.util.List. It does not make the code bigger or slower; nothing is "loaded" by an import.

Two things you never need to import: classes in java.lang (String, Integer, System, Math) and classes in the same package as yours.

The wildcard form import java.util.*; works but is discouraged in shared code, for one concrete reason — it hides where a type came from, and when two wildcard imports both offer a List the compiler rejects the file and you have to work out why. Let your IDE manage explicit imports.

When two classes with the same simple name are genuinely needed in one file, one of them must be fully qualified:

import java.util.Date;

class Demo {
    void run() {
        Date modern = new Date();
        java.sql.Date legacy = new java.sql.Date(0);   // no second import possible
        System.out.println(modern + " " + legacy);
    }
}

The four access modifiers

Access control is where packages stop being organisational and start being structural. There are four levels, and one of them has no keyword:

ModifierSame classSame packageSubclass, other packageAnywhere
privateyes
(none) — package-privateyesyes
protectedyesyesyes
publicyesyesyesyes
public class Order {
    private String secret;          // this class only
    String internal;                // package-private — anything in this package
    protected String forHeirs;      // package, plus subclasses anywhere
    public String id;               // everyone
}

The default is package-private, not public. Most people assume a field with no modifier is public; it is not, and that default is a useful one. A class or method with no modifier is visible to its own package and invisible outside it — which is exactly right for a helper that exists to serve its neighbours.

Use it deliberately. If a class is only used by others in the same package, leaving it package-private means you can change or delete it without breaking anyone.

protected is narrower than it looks and worth using sparingly: it exposes a member to every subclass anywhere, forever, which makes it part of your public API in practice.

Laying out a real project

Two common approaches. By layer groups classes by what kind of thing they are:

com.lovemesomecoding.shop
  controller/   OrderController, ProductController
  service/      OrderService, ProductService
  repository/   OrderRepository, ProductRepository
  model/        Order, Product

By feature groups them by what part of the business they serve:

com.lovemesomecoding.shop
  order/        OrderController, OrderService, OrderRepository, Order
  product/      ProductController, ProductService, ProductRepository, Product
  shared/       config, error handling

By-layer is what most tutorials show and it is fine for a small application. By-feature scales better: everything about orders is in one place, a change to orders touches one directory, and — the real payoff — most of those classes can be package-private, because their only callers are in the same package. With by-layer, every service has to be public just so a controller in another package can reach it.

Pick one and apply it consistently. Mixing the two produces a tree where nobody can guess where anything lives.

Compiling with packages

# from the source root
javac -d out com/lovemesomecoding/orders/Order.java

# run using the FULLY QUALIFIED class name
java -cp out com.lovemesomecoding.orders.Order

Note the run command: once a class is in a package, its real name includes that package. In practice a build tool such as Maven or Gradle does all of this, but it is worth seeing once so the "could not find or load main class" error makes sense when you meet it.

Modules, and why you can mostly ignore them

Java 9 added a second, larger unit of organisation: the module, declared in a module-info.java at the source root.

module com.lovemesomecoding.shop {
    requires java.sql;                       // what this module depends on
    exports com.lovemesomecoding.shop.order; // what other modules may use
}

A module states its dependencies and — the important half — which of its packages are visible outside it. A public class in a package the module does not export is unreachable from other modules, which finally makes "public" mean something less than "public to the entire world".

It matters enormously for the JDK itself, which is how java.base, java.sql and the rest are separated. For ordinary application code most teams never write a module-info.java, and a build tool's dependency management covers the same ground. Recognise it when you see it; do not go looking for it on day one.

Next

That completes the structural half of the language. Collections is next — the containers you will use in essentially every program you write.