Dataslope logoDataslope

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.java

Picking package names

Java packages are conventionally named in lowercase, with words separated by dots, starting with a reverse-domain prefix to avoid clashes:

ProjectCommon prefix
Your university CS classedu.your-school.netid
Personal projectdev.you.project
Companycom.company.product

Inside that prefix, name packages by what they're aboutaccount, 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:

ModifierVisible from
privateThe declaring class only
(no modifier)package-privateOther classes in the same package
protectedSame package, and subclasses anywhere
publicAnywhere

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.

LayerSample classesSample packages
PresentationMenuPrinter, OrderForm, LibraryClicom.lib.cli
ApplicationLibraryService, BorrowUseCasecom.lib.service
DomainBook, Member, Loan, Catalogcom.lib.model
InfrastructureBookFileRepository, JdbcLoanRepositorycom.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:

  1. Model has no outgoing arrows to other packages here. It is the most stable, most reusable code in the system.
  2. Service is the orchestrator. It assembles work from the model and from infrastructure (payments).
  3. CLI is replaceable. Swapping it for a web layer means writing a new com.shop.web package; nothing else changes.
  4. Payments has an interface (PaymentGateway) and concrete implementations. The service depends on the interface, not on Stripe.

Single file vs. multi-package projects

How do you know when to split a class into a new package?

SignLikely action
Two unrelated subjects in the same packageSplit into two packages
A class is only used within its packageMake it package-private
Classes within a package import very little from outsideA good sign — high cohesion
Every package imports from every otherA 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.

Code Block
Java 8 (Update 492)

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

QuestionSelect one

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

QuestionSelect one

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

QuestionSelect one

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

On this page