SECTION 1
Historical Context & Motivation
In the earliest days of programming, developers wrote entire programs inside a single file. As programs grew from a few dozen lines to hundreds of thousands, this approach became unmanageable. Imagine trying to find one sentence in a novel where every chapter is jammed onto one page — that is what reading a large, single-file program feels like. The need to organize code into separate, logical pieces drove the evolution of modern programming languages, including Java.
1960s • Structured Programming
1980s • Object-Oriented Programming
1995 • Java Is Released
2000s–Present • Modern IDEs & Build Tools
The central question this lesson addresses is straightforward: how do you break a Java program into multiple classes and files, and why does it matter? By the end, you will be comfortable creating multi-file Java projects and understanding how the pieces connect.
SECTION 2
Core Principles & Definitions
Before diving into code, let's nail down the key ideas that guide how Java programs are organized. Each principle below tackles one aspect of keeping your codebase clear and manageable.
One Public Class Per File
Dog.java must contain public class Dog.Separation of Concerns
Packages for Grouping
package myapp.models; and match it to the directory structure on disk.Import Statements
import myapp.models.Student;. Classes in the same package can see each other without imports.Access Modifiers
KEY TAKEAWAY
SECTION 3
Visual Explanation — Project Structure
The diagram below shows a typical multi-file Java project. Notice how files are organized into directories that match their package names, and how the Main class sits at the top level while model classes are grouped in a models sub-package.
myapp containing Main.java and a sub-package models containing Student.java and Course.java. The code snippets at the bottom show how each file declares its package and how Main imports the model classes.In the diagram, each .java file contains exactly one public class. The directory structure on disk mirrors the package declarations inside each file. The Main.java file uses an import statement to pull in the classes from the models package, allowing it to create Student and Course objects.
SECTION 4
How It Works — Compilation & Linking
When your Java project spans multiple files, you need to understand how the Java compiler (javac) processes everything. The compiler reads each .java file, checks that the class name matches the filename, resolves import statements to locate referenced classes, and then produces a corresponding .class file containing bytecode. The Java Virtual Machine (JVM) then runs the bytecode at runtime.
Step-by-Step Compilation Flow
- Write — Create separate
.javafiles, one public class per file, with matching filenames. - Compile — Run
javac myapp/Main.java. The compiler automatically finds and compiles dependencies likeStudent.javaandCourse.java. - Resolve — The compiler uses package declarations and import statements to locate all referenced classes. If a class cannot be found, you get a compile-time error.
- Run — Execute with
java myapp.Main. The JVM loads each.classfile as needed.
Filename Rule
GradeBook, the file must be named GradeBook.java — not gradebook.java or Gradebook.java. Java is case-sensitive. Getting this wrong is one of the most common beginner mistakes.An important detail is that you can have non-public (default access) classes in the same file as a public class. However, this is generally discouraged because it hides classes in unexpected places. The standard practice is one class per file, period.
SECTION 5
Packages & Access Modifiers in Detail
As your project grows, you will want to group related classes into packages. A package is both a logical grouping in your source code and a physical folder on your hard drive. Understanding how packages interact with access modifiers is essential for controlling which classes can see and use each other.
myapp.models can access each other's public, protected, and default members. The Main class in a different package can only access public members.| Modifier | Same Class | Same Package | Subclass (Other Pkg) | Any Class |
|---|---|---|---|---|
public | ✅ | ✅ | ✅ | ✅ |
protected | ✅ | ✅ | ✅ | ❌ |
| default (no keyword) | ✅ | ✅ | ❌ | ❌ |
private | ✅ | ❌ | ❌ | ❌ |
Java Access Modifier Visibility Summary
SECTION 6
Worked Example — Building a Multi-File Project
Let's walk through creating a small Java project from scratch. We will build a simple school roster application with three classes spread across two packages.
Creating a Multi-File School Roster App
- 1
Step 1 — Plan the Classes
We need three classes:Student(holds a student's name and GPA),Classroom(holds a list of students and a room number), andMain(the entry point that creates objects and prints results). We will putStudentandClassroomin theschool.modelspackage andMainin theschoolpackage.3 classes, 2 packages, 3 files - 2
Step 2 — Create Directory Structure
Create the folder hierarchy to match the packages. On your file system, make aschool/folder containingMain.java, and inside it amodels/folder containingStudent.javaandClassroom.java.school/Main.java, school/models/Student.java, school/models/Classroom.java - 3
Step 3 — Write Student.java
Begin with the package declaration:package school.models;. Then define the class with private fields (nameandgpa), a constructor, and public getter methods. Making fields private and providing getters follows the principle of encapsulation — only the Student class controls how its data is accessed.public class Student { private String name; private double gpa; ... } - 4
Step 4 — Write Classroom.java
Same package declaration:package school.models;. This class holds anArrayList<Student>and a methodaddStudent(Student s). Because Classroom and Student are in the same package, Classroom can reference Student without an import. We also add a methodgetAverageGPA()that loops through students and computes the mean.public class Classroom { private ArrayList<Student> students; ... } - 5
Step 5 — Write Main.java & Compile
Declarepackage school;and addimport school.models.*;at the top. In themainmethod, create a Classroom, add a few Students, and print the average GPA. Compile from the parent directory withjavac school/Main.javaand run withjava school.Main.Output:Average GPA: 3.5
SECTION 7
Strengths & Limitations of Multi-File Organization
Splitting code into multiple files and classes is overwhelmingly beneficial, but it is worth understanding the trade-offs, especially for small versus large projects.
| Strengths | Limitations |
|---|---|
| Readability — Each file is short and focused, making it easy to find what you need. | Overhead for tiny programs — For a 20-line script, creating three files feels like overkill. |
| Reusability — A well-written Student class can be used in many different programs without changes. | Navigation — With dozens of files, you need good tools (an IDE) to jump between them efficiently. |
| Team collaboration — Multiple developers can work on different files simultaneously without merge conflicts. | Circular dependencies — If class A depends on class B and class B depends on A, you can create confusing tangles. Good design avoids this. |
| Testability — Individual classes can be tested independently using tools like JUnit. | Learning curve — Beginners must understand packages, imports, and access modifiers before multi-file projects feel natural. |
Multi-File Organization: Strengths vs. Limitations
KEY TAKEAWAY
SECTION 8
Connection to Advanced Concepts
The skills you are building now lay the groundwork for more sophisticated Java features and real-world software engineering practices. Below is a comparison of what you have learned versus what comes next as you advance.
| What You Know Now | What Comes Next |
|---|---|
| Organizing classes into packages with folders | Java Modules (JPMS) — Introduced in Java 9, modules group packages together and add another layer of encapsulation for large-scale applications. |
| Using import to reference classes from other packages | Dependency management — Tools like Maven and Gradle automatically download and manage thousands of external libraries (JARs) that your project can import. |
| One class per file with clear responsibilities | Design patterns — Patterns like MVC (Model-View-Controller) formalize how to divide responsibilities across many classes in a predictable structure. |
| Compiling multiple files with javac | Build automation — Build tools compile, test, and package your entire project with a single command, handling hundreds of source files automatically. |
Current Knowledge vs. Advanced Topics
The essential insight is that everything in professional Java development scales up from what you are learning here. A project with 3 files and 2 packages follows the exact same rules as a project with 3,000 files and 200 packages. Master the fundamentals now, and the advanced tools will feel like natural extensions rather than intimidating new concepts.
SECTION 9
Practice Problems
Question 1: CONCEPTUAL
BankAccount must be saved in a file called BankAccount.java. What problem does this naming rule solve?Show answer
import bank.BankAccount, the compiler knows to look for bank/BankAccount.java (or BankAccount.class) immediately. Without this rule, the compiler would have to scan every file to find which one contains that class, slowing down compilation and making organization unpredictable.Question 2: BASIC CALCULATION
Rectangle in package shapes.geometry and a class DrawingApp in package shapes. Write the exact directory structure (folders and file names) and the import statement that DrawingApp needs to use Rectangle.Show answer
shapes/DrawingApp.java and shapes/geometry/Rectangle.java. Inside DrawingApp.java, you would add import shapes.geometry.Rectangle; at the top (after the package declaration package shapes;). Even though geometry is inside the shapes folder, Java does NOT treat sub-packages as "part of" the parent package — you must still explicitly import.Question 3: INTERMEDIATE
game.characters: Hero with a private int health field and a public int getHealth() method, and Enemy that wants to read the hero's health. Can Enemy directly access hero.health? Explain why or why not, and describe the correct approach.Show answer
Enemy cannot directly access hero.health because the field is declared private. Private members are only visible within the class that defines them, regardless of whether the classes are in the same package. Being in the same package would help if the field had default (package-private) access, but private is the most restrictive level. The correct approach is for Enemy to call hero.getHealth(), which is public and returns the health value safely.Question 4: APPLIED
Show answer
music.models for two model classes: Song (with private fields title, artist, and durationSeconds, plus public getters) and Playlist (with a private ArrayList<Song> and public methods addSong(), getTotalDuration(), and printAll()). In package music, a MusicApp class imports music.models.*, creates Song objects, adds them to a Playlist, and prints the total duration. Fields are private to enforce encapsulation — nobody outside Song should be able to change a title directly.Question 5: CRITICAL THINKING
Show answer
Utility class and then uses a library that also has a Utility class, there is an unresolvable naming collision. Packages act as namespaces that prevent this. Second, all classes would have default (package-level) access to each other's non-private members, which means there is no way to hide implementation details between groups of classes. For example, a DatabaseHelper's internal methods would be visible to UI classes, inviting accidental misuse. Third, navigation becomes a nightmare: with 50+ files in a single flat folder, finding the right file requires remembering exact names, whereas packages like app.ui, app.data, and app.models create a logical map of the project. In professional settings, all Java projects use packages, and skipping them would make collaboration with other developers extremely difficult.SUMMARY
Lesson Summary
Summary
Organizing a Java project across multiple classes and files is a fundamental skill that scales from homework assignments to professional software. Each public class lives in its own .java file whose name matches the class name exactly. Related classes are grouped into packages, which correspond to folder directories on disk. When a class needs to use another class from a different package, it references it through an import statement.
Access modifiers (public, private, protected, and default) control which classes can see each other's members, enforcing encapsulation and separation of concerns. This structure makes code more readable, reusable, testable, and easier to maintain — whether you are working alone or on a team. The compilation process handles multi-file projects seamlessly: javac resolves imports automatically, and java loads each class file as needed at runtime.