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.
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.
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
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.
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.
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.
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 | β | β | β | β |
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.
Student (holds a student's name and GPA), Classroom (holds a list of students and a room number), and Main (the entry point that creates objects and prints results). We will put Student and Classroom in the school.models package and Main in the school package.school/ folder containing Main.java, and inside it a models/ folder containing Student.java and Classroom.java.school/Main.java, school/models/Student.java, school/models/Classroom.javapackage school.models;. Then define the class with private fields (name and gpa), 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; ... }package school.models;. This class holds an ArrayList<Student> and a method addStudent(Student s). Because Classroom and Student are in the same package, Classroom can reference Student without an import. We also add a method getAverageGPA() that loops through students and computes the mean.public class Classroom { private ArrayList<Student> students; ... }package school; and add import school.models.*; at the top. In the main method, create a Classroom, add a few Students, and print the average GPA. Compile from the parent directory with javac school/Main.java and run with java school.Main.Average GPA: 3.5Strengths & 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. |
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. |
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.
Practice Problems
BankAccount must be saved in a file called BankAccount.java. What problem does this naming rule solve?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.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.Lesson 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.