Packages and Architecture
Organizing many classes into packages and layers — how OOP scales from a handful of classes to a real system
A real Java program has dozens or hundreds of classes. Without organization, that becomes a wall of files no human can navigate. Packages are Java's tool for grouping related classes into named units. Layered architecture is a broader pattern for grouping packages into a sensible whole.
This page is about scaling OOP from "ten files" to "a thousand files" — without losing the qualities that make OOP worth using.
Note about the playgrounds on this page
The CheerpJ-based Java code blocks in this course don't model
packages literally — they put all files in one flat workspace. So
the examples below describe packages in the way a real multi-file
Java project would lay them out, but you can still run the code in
the playground without typing the package keyword. We will use
package lines in the diagrams and prose, and a comment in each
file in the code blocks shows where it would live.
What a package is
A Java package is a namespace — a labeled folder of related
classes. The package name is part of a class's fully qualified
name. The class java.util.ArrayList lives in the package
java.util. Class names only need to be unique within a package.
The folder layout on disk mirrors the package:
src/
└── com/
└── bank/
├── account/
│ ├── Account.java
│ ├── Transaction.java
│ └── Statement.java
├── customer/
│ ├── Customer.java
│ └── Address.java
└── payments/
├── Payment.java
└── Gateway.javaPicking package names
Java packages are conventionally named in lowercase, with words separated by dots, starting with a reverse-domain prefix to avoid clashes:
| Project | Common prefix |
|---|---|
| Your university CS class | edu.your-school.netid |
| Personal project | dev.you.project |
| Company | com.company.product |
Inside that prefix, name packages by what they're about — account,
customer, payments — not by what kind of class they contain
(avoid utils, helpers, objects).
Visibility, revisited
Recall the four Java access modifiers. Packages give two of them their meaning:
| Modifier | Visible from |
|---|---|
private | The declaring class only |
| (no modifier) — package-private | Other classes in the same package |
protected | Same package, and subclasses anywhere |
public | Anywhere |
The default — package-private — is more useful than beginners
realize. It lets the classes within a package share helpers without
exposing them to the whole program. Reach for public only when a
class is part of your package's external API.
TxValidator is package-private. Within com.bank.account it's
freely available; from outside, it's invisible. That isolation is
real encapsulation — at the package level.
Layered architecture
A layer is a horizontal slice of a system that has a single kind of responsibility. A typical small Java app has three or four layers:
The cardinal rule: dependencies point inward. The UI may depend on application services. Application services may depend on the domain model. The domain model — the actual business objects, the heart of the system — should depend on nothing else in your code.
This is sometimes called a hexagonal or clean architecture. The headline benefit: you can swap the UI (CLI to web to mobile) without touching the domain, and swap the database (in-memory to SQL to cloud) without touching either.
| Layer | Sample classes | Sample packages |
|---|---|---|
| Presentation | MenuPrinter, OrderForm, LibraryCli | com.lib.cli |
| Application | LibraryService, BorrowUseCase | com.lib.service |
| Domain | Book, Member, Loan, Catalog | com.lib.model |
| Infrastructure | BookFileRepository, JdbcLoanRepository | com.lib.persistence |
A small but realistic layout
Imagine the order system from the previous page, organized for
growth. Each file's first line would be a package declaration.
A few principles to notice:
- Model has no outgoing arrows to other packages here. It is the most stable, most reusable code in the system.
- Service is the orchestrator. It assembles work from the model and from infrastructure (payments).
- CLI is replaceable. Swapping it for a web layer means
writing a new
com.shop.webpackage; nothing else changes. - Payments has an interface (
PaymentGateway) and concrete implementations. The service depends on the interface, not onStripe.
Single file vs. multi-package projects
How do you know when to split a class into a new package?
| Sign | Likely action |
|---|---|
| Two unrelated subjects in the same package | Split into two packages |
| A class is only used within its package | Make it package-private |
| Classes within a package import very little from outside | A good sign — high cohesion |
| Every package imports from every other | A bad sign — low cohesion, high coupling |
The goal is high cohesion within packages (classes in a package belong together) and low coupling between packages (packages don't reach into each other unnecessarily).
Imports
When a class in one package needs a class from another, it imports it:
package com.shop.service;
import com.shop.model.Order;
import com.shop.model.Menu;
import com.shop.payments.Payment;
public class OrderService {
private final Menu menu;
private final Payment payment;
// ...
}Classes in the same package are visible to each other without an
import. The java.lang package (which contains String, Integer,
System, etc.) is also imported implicitly into every Java file.
A tiny "many-classes" example, flat for the playground
Even though the playground keeps everything flat, we can still
design with packages and layers in mind. Here is a small,
deliberately under-decorated example: a domain model (Book,
Catalog), an application service (LibraryService), and a
"CLI"-style presentation (Main). Notice that LibraryService never
prints anything — it just orchestrates. Printing is a presentation
concern.
Even at this size, you can see the shape of a real project: the
domain knows about books, the service orchestrates queries, and
the presentation is the only thing that calls System.out. Add
a database tomorrow → write a new Catalog implementation in an
"infrastructure" package. Add a web UI → write a new presentation
package. The domain doesn't move.
Test your understanding
Which is the most useful default discipline for choosing access modifiers?
Make every class public so nothing is hidden by accident
Make every member protected because it's "in the middle"
Make everything as restrictive as possible (private by default, package-private when needed) and widen only when there's a real reason
Always use package-private for everything
In a layered architecture, which way should dependencies point?
From the domain inward to the database
Domain should depend on the presentation layer
From the outer layers (presentation, infrastructure) toward the inner core (application, domain) — the domain depends on nothing else of yours
All layers should depend on every other layer equally
What is the goal expressed by "high cohesion, low coupling"?
Maximize the number of imports across packages
Combine all classes into one giant package so nothing has to import anything
Classes within a package should belong together; packages should depend on each other as little as possible
Use the keyword public on as many members as possible