Java
Programming
Multiple Class Declarations
Coding Practices
Software Development

Java Multiple class declarations in one file

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Java allows multiple class declarations in a single .java file, but only one of them can be public, and that public class must match the filename. All other classes in the file must be package-private (no access modifier). The compiler generates a separate .class file for each class regardless of how they are organized in source files.

The One-Public-Class Rule

The Java Language Specification enforces a strict rule: if a file contains a public class, the file must be named after that class. This means at most one public class per file.

java
1// File: OrderProcessor.java
2
3public class OrderProcessor {  // Must match filename
4    public void process(Order order) {
5        OrderValidator validator = new OrderValidator();
6        if (validator.isValid(order)) {
7            // process the order
8        }
9    }
10}
11
12class OrderValidator {  // Package-private, same file is allowed
13    boolean isValid(Order order) {
14        return order != null && order.getItems().size() > 0;
15    }
16}
17
18class Order {  // Another package-private class in the same file
19    private List<String> items;
20    List<String> getItems() { return items; }
21}

Compiling OrderProcessor.java produces three .class files:

  • OrderProcessor.class
  • OrderValidator.class
  • Order.class

Each class is fully independent at the bytecode level. The fact that they share a source file is purely a source-code organization choice.

What Happens If You Break the Rule

Attempting to declare two public classes in one file causes a compilation error:

java
1// File: MyApp.java
2
3public class MyApp {          // OK: matches filename
4    // ...
5}
6
7public class MyHelper {       // ERROR: The public type MyHelper must be
8    // ...                    //        defined in its own file
9}

The compiler enforces this because it uses the filename to locate public classes during compilation. Without this convention, the compiler would need to scan every file to find a class definition.

Four Types of Classes in a Single File

Java supports four distinct kinds of class declarations within a file, each with different visibility and use cases:

1. Top-Level Public Class

java
1// File: UserService.java
2public class UserService {
3    public User findById(long id) { /* ... */ }
4}

One per file, visible to all packages. This is the standard approach.

2. Top-Level Package-Private Class

java
1// File: UserService.java
2public class UserService {
3    public User findById(long id) {
4        return new UserCache().get(id);
5    }
6}
7
8class UserCache {  // Only visible within the same package
9    User get(long id) { /* ... */ return null; }
10}

Visible only within the same package. Useful for implementation details that support the public class.

3. Inner Class (Non-Static Nested)

java
1public class LinkedList<T> {
2    private Node<T> head;
3
4    // Inner class: has implicit reference to outer LinkedList instance
5    private class Node<T> {
6        T data;
7        Node<T> next;
8    }
9}

Tied to an instance of the enclosing class. Has access to all fields and methods of the outer class, including private members.

4. Static Nested Class

java
1public class Response {
2    private int statusCode;
3    private String body;
4
5    // Static nested class: no reference to outer instance
6    public static class Builder {
7        private int statusCode;
8        private String body;
9
10        public Builder statusCode(int code) {
11            this.statusCode = code;
12            return this;
13        }
14
15        public Builder body(String body) {
16            this.body = body;
17            return this;
18        }
19
20        public Response build() {
21            Response r = new Response();
22            r.statusCode = this.statusCode;
23            r.body = this.body;
24            return r;
25        }
26    }
27}

Acts like a regular class but scoped inside another. Commonly used for builders, iterators, and entry types (like Map.Entry).

Comparison of Declaration Types

Declaration TypeAccess ModifierVisibilitySeparate .class FileCan Access Outer Instance
Public top-levelpublicAll packagesYesN/A
Package-private top-level(none)Same package onlyYesN/A
Inner classAnyDepends on modifierYes (Outer$Inner.class)Yes
Static nested classAnyDepends on modifierYes (Outer$Nested.class)No
Local class (in method)(none)Enclosing method onlyYes (Outer$1Local.class)Yes (if non-static method)
Anonymous class(none)Expression onlyYes (Outer$1.class)Yes (if non-static context)

When Multiple Top-Level Classes Make Sense

There are a few legitimate scenarios for putting multiple classes in one file:

Small, Tightly Coupled Helper Classes

When a helper class is only a few lines and only used by the primary class in the file:

java
1// File: CsvParser.java
2public class CsvParser {
3    public List<CsvRow> parse(String content) {
4        List<CsvRow> rows = new ArrayList<>();
5        for (String line : content.split("\n")) {
6            rows.add(new CsvRow(line.split(",")));
7        }
8        return rows;
9    }
10}
11
12class CsvRow {  // Trivial data holder, only used by CsvParser
13    final String[] columns;
14    CsvRow(String[] columns) { this.columns = columns; }
15    String get(int index) { return columns[index]; }
16}

Sealed Class Hierarchies (Java 17+)

Sealed classes often define their permitted subclasses in the same file for cohesion:

java
1// File: Shape.java
2public sealed class Shape permits Circle, Rectangle, Triangle {
3    abstract double area();
4}
5
6final class Circle extends Shape {
7    double radius;
8    Circle(double radius) { this.radius = radius; }
9    double area() { return Math.PI * radius * radius; }
10}
11
12final class Rectangle extends Shape {
13    double width, height;
14    Rectangle(double w, double h) { this.width = w; this.height = h; }
15    double area() { return width * height; }
16}
17
18final class Triangle extends Shape {
19    double base, height;
20    Triangle(double b, double h) { this.base = b; this.height = h; }
21    double area() { return 0.5 * base * height; }
22}

This pattern keeps the entire type hierarchy visible in one place, which is particularly natural for algebraic data types used with pattern matching.

Prototyping and Competitive Programming

During prototyping or competitive programming, keeping everything in one file reduces friction:

java
1// File: Solution.java
2public class Solution {
3    public static void main(String[] args) {
4        Graph g = new Graph(5);
5        g.addEdge(0, 1);
6        g.addEdge(1, 2);
7        System.out.println(g.bfs(0));
8    }
9}
10
11class Graph {
12    List<List<Integer>> adj;
13    Graph(int n) {
14        adj = new ArrayList<>();
15        for (int i = 0; i < n; i++) adj.add(new ArrayList<>());
16    }
17    void addEdge(int u, int v) { adj.get(u).add(v); adj.get(v).add(u); }
18    List<Integer> bfs(int start) { /* ... */ return new ArrayList<>(); }
19}

When to Avoid Multiple Classes in One File

For production code, the standard practice is one class per file. Here is why:

  • IDE navigation. IDEs map filenames to class names. If OrderValidator is inside OrderProcessor.java, pressing "Go to Class" for OrderValidator does not take you to the right file intuitively.
  • Version control. Changes to OrderValidator show up as diffs on OrderProcessor.java, making commit history harder to follow.
  • Code review. Reviewers expect to find a class in a file with the matching name. Hunting through files for package-private classes slows down reviews.
  • Refactoring. Extracting a package-private class into its own file later requires updating every import in the package.

Common Pitfalls

Assuming package-private classes are invisible. Package-private classes are accessible to every class in the same package, not just the class they share a file with. If another class in the package references your "helper" class, you have created an unintended coupling.

Forgetting that each class gets its own .class file. Even though three classes share one .java file, the compiler outputs three .class files. Deployment tools and classloaders operate on .class files, so the source file organization is invisible at runtime.

Using multiple classes to avoid proper package structure. If you find yourself putting many related classes in one file, that is usually a sign you need a sub-package instead. Create a new package and give each class its own file.

Inner classes holding references to the outer instance. Non-static inner classes implicitly hold a reference to the enclosing object, which prevents garbage collection of the outer instance. Use static nested classes unless you specifically need access to the outer instance.

Sealed class permits across files. In Java 17+, sealed classes can permit subclasses in other files (using the permits clause), but those subclasses must be in the same package. You do not need to put them in the same file.

Summary

Java allows multiple class declarations in one file, with the constraint that at most one can be public (and must match the filename). Package-private top-level classes, inner classes, static nested classes, local classes, and anonymous classes all provide different scoping mechanisms. For production code, prefer one class per file for maintainability and tooling support. Use multiple classes in one file for sealed type hierarchies, small tightly-coupled helpers, and prototyping scenarios where single-file convenience outweighs organizational overhead.


Course illustration
Course illustration

All Rights Reserved.