☕ Java

Java User-Defined Packages: Create, Compile, Structure

Learn how to create user-defined packages in Java: package declaration, directory layout, compiling with javac -d, access modifiers, naming rules and structure patterns.

User-Defined Packages in 30 Seconds

A user-defined package is a package you create to group your own related classes and interfaces. You create one in three steps: 1. Put a package declaration at the top of the file, for example: package com.example.service; 2. Save the file in a matching directory: com/example/service/OrderService.java 3. Compile with the -d flag so the .class files land in the matching output directory, and run the class by its fully qualified name. Rules to remember: - The package statement must be the first statement in the file (only comments may come before it). - Package names are lowercase and conventionally start with a reversed domain name (com.example). - Classes in the same package can use each other by simple name and can access each other's package-private members. - Classes in other packages need an import or a fully qualified name, and can only see public members (or protected members through inheritance).

Declaring and Using User-defined Packages

Declaring a package is the first line of any Java source file (before imports). The statement package com.example.service; tells the compiler and JVM that the class in this file belongs to the com.example.service namespace. By convention, and as required by Maven, Gradle and the standard classpath lookup, the file for com.example.service.OrderService lives at com/example/service/OrderService.java relative to the source root. The location of the .java file and the declared package must agree. If you declare package com.example.service but keep the file in a different directory, javac can still compile that one file when you pass its path directly, but the problems start afterwards: other classes cannot find it through -sourcepath ("cannot find symbol" or "package does not exist"), and the JVM reports errors such as "wrong name" or NoClassDefFoundError when the .class file sits in a directory that does not match its package. IDEs flag the mismatch immediately. When one class needs a class from a different package, it either imports it or uses the fully qualified name (com.example.model.Order). Inside the same package, classes refer to each other by simple name with no import. That is one reason related classes belong in the same package: shorter code and access to package-private members. Compile with -d to tell javac where to write the output. It creates the package directories for you: javac -sourcepath src -d out src/com/example/App.java compiles App and every source it depends on, then writes the .class files under out/com/example/... Run with the classpath pointing at the output root and the fully qualified class name: java -cp out com.example.App. Note that the class you run must contain a public static void main(String[] args) method.
Java
// ── File: src/com/example/model/Order.java ───────────────────────────
package com.example.model;      // FIRST statement — before imports

public class Order {
    private final String id;
    private final String customerId;
    private final double total;

    public Order(String id, String customerId, double total) {
        this.id         = id;
        this.customerId = customerId;
        this.total      = total;
    }

    public String getId()         { return id;         }
    public String getCustomerId() { return customerId; }
    public double getTotal()      { return total;      }
}

// ── File: src/com/example/service/OrderService.java ───────────────────
package com.example.service;

import com.example.model.Order;         // import from another package

public class OrderService {

    private static int nextId = 1001;

    // Can use Order by simple name because it was imported:
    public Order createOrder(String customerId, double total) {
        return new Order(generateId(), customerId, total);
    }

    // Without an import, the fully qualified name is required:
    public com.example.model.Order findOrder(String id) {
        return null;   // lookup omitted in this example
    }

    private String generateId() {
        return "ORD-" + nextId++;
    }
}

// ── File: src/com/example/App.java  (has the main method) ─────────────
package com.example;

import com.example.model.Order;
import com.example.service.OrderService;

public class App {
    public static void main(String[] args) {
        OrderService service = new OrderService();
        Order order = service.createOrder("C-42", 99.5);
        System.out.println("Created " + order.getId()
                + " for " + order.getCustomerId()
                + ", total " + order.getTotal());
    }
}

// ── Directory structure: ──────────────────────────────────────────────
//  src/
//  └── com/
//      └── example/
//          ├── App.java
//          ├── model/
//          │   └── Order.java
//          └── service/
//              └── OrderService.java
//
// ── Compile (javac finds Order and OrderService through -sourcepath): ─
//  javac -sourcepath src -d out src/com/example/App.java
//
// ── Resulting output: ─────────────────────────────────────────────────
//  out/com/example/App.class
//  out/com/example/model/Order.class
//  out/com/example/service/OrderService.class
//
// ── Run (fully qualified name, classpath = output root): ──────────────
//  java -cp out com.example.App
//
// ── Output: ───────────────────────────────────────────────────────────
//  Created ORD-1001 for C-42, total 99.5

Access Modifiers Across Package Boundaries

Packages are the boundary that access modifiers are measured against. Choosing the right modifier is what makes a package a real unit of encapsulation instead of just a folder. Access by modifier: - private: visible only inside the same class. - default (no modifier, called package-private): visible to the same class and any class in the same package. - protected: visible to the same package, plus subclasses in other packages (through inheritance). - public: visible everywhere. Design guidance: make a class public only if other packages need it. Keep helpers, validators and calculators package-private so they cannot be used (or depended on) from outside. This keeps the public surface of each package small, which makes refactoring safer. Two details that surprise people: - Importing a package does not import its sub-packages. import com.example.*; does not give you com.example.model.Order. - Packages are flat namespaces. com.example and com.example.model have no special access to each other: a class in one cannot see the package-private members of the other.
Java
// File: src/com/example/order/Order.java
package com.example.order;

public class Order {                       // public: part of the package API
    String internalNotes = "";             // package-private field
    protected double discount;             // visible to subclasses elsewhere
    public double getTotal() { return 0; } // public API

    double applyRounding(double v) {       // package-private helper
        return Math.round(v * 100) / 100.0;
    }
}

// File: src/com/example/order/OrderValidator.java
package com.example.order;

class OrderValidator {                     // package-private class: hidden from other packages
    boolean isValid(Order o) {
        // Same package, so package-private members of Order are accessible:
        return o.getTotal() >= 0 && o.internalNotes != null;
    }
}

// File: src/com/example/billing/Invoice.java
package com.example.billing;

import com.example.order.Order;
// import com.example.order.OrderValidator;   // COMPILE ERROR: not public

public class Invoice {
    void print(Order o) {
        System.out.println(o.getTotal());      // OK: public
        // System.out.println(o.internalNotes); // ERROR: package-private
        // o.applyRounding(1.234);              // ERROR: package-private
    }
}

Package Naming Conventions and Maven Layout

Naming rules and conventions: - Use all lowercase letters, with no underscores or capital letters (com.example.orderservice, not com.example.OrderService). - Start with your reversed internet domain (com.mycompany.project) to avoid clashes with other libraries. If you have no domain, use something unique such as io.github.yourusername. - Do not use Java keywords as package segments (com.example.new, com.example.default are illegal), and do not start a segment with a digit. - Never use the names java or javax as a root; they are reserved for the platform. - Avoid the default (unnamed) package for anything beyond a throwaway test. Classes in the default package cannot be imported by classes in named packages. In a Maven or Gradle project, the directory layout is fixed: production code lives under src/main/java and tests under src/test/java. The package is derived from the directories below those roots. Test classes should use the same package as the class under test, which lets them access package-private members without making them public.
Java
// Standard Maven / Gradle layout:
//
//  my-app/
//  ├── pom.xml
//  └── src/
//      ├── main/
//      │   └── java/
//      │       └── com/example/order/
//      │           ├── Order.java
//      │           ├── OrderService.java
//      │           └── OrderValidator.java     // package-private
//      └── test/
//          └── java/
//              └── com/example/order/          // SAME package as the code under test
//                  └── OrderValidatorTest.java // can see package-private OrderValidator
//
// Good package names:   com.example.order      com.example.payment.gateway
// Bad package names:    com.example.Order      com.example.my_utils     Com.Example

// Test in the same package can reach package-private code:
package com.example.order;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;

class OrderValidatorTest {
    @Test
    void validOrderPasses() {
        assertTrue(new OrderValidator().isValid(new Order()));
    }
}

Package Structure Design Patterns

The way you structure packages shapes how navigable and maintainable a codebase is. Two major philosophies exist: package-by-layer and package-by-feature, and real projects often use a combination. Package-by-layer groups classes by their architectural role. All controllers together, all services together, all repositories together. This is the traditional structure taught in most tutorials and used in many enterprise codebases. The advantage is that every developer knows exactly where to find any type of class. The disadvantage is that changing a feature requires touching many packages: all the classes for the "orders" feature are spread across controller, service, repository and model. It also forces almost everything to be public, because classes that collaborate live in different packages. Package-by-feature groups all classes for a feature together: the controller, service, repository, model and exceptions for orders all live in com.example.order. This aligns packages with the business domain rather than the technical architecture. When you work on a feature, all the relevant code is in one place, and only the classes that other features truly need must be public; the rest can stay package-private. The disadvantage is that shared utilities and cross-cutting concerns need separate packages. A hybrid approach is increasingly popular: package by feature at the top level, with sub-packages for technical layers within each feature. com.example.order contains the feature as a whole; com.example.order.service, com.example.order.repository and com.example.order.dto contain the technical layers within it. This gives both the navigability of feature packages and the clarity of layer packages, at the cost of making more classes public again, since layers sit in different sub-packages. Whichever you pick, the package structure should reflect how developers think about and navigate the code, not the implementation technology. Names like com.example.hibernate or com.example.springboot are poor choices because they tie package names to technology that may change. Avoid circular dependencies between packages (order depends on user, user depends on order). They make the code hard to test and to split later. If two packages need each other, extract the shared piece into a third package or introduce an interface.
Java
// ── Package-by-layer structure: ───────────────────────────────────────
//
//  com.example
//  ├── controller
//  │   ├── OrderController.java
//  │   └── UserController.java
//  ├── service
//  │   ├── OrderService.java
//  │   └── UserService.java
//  ├── repository
//  │   ├── OrderRepository.java
//  │   └── UserRepository.java
//  ├── model
//  │   ├── Order.java
//  │   └── User.java
//  └── exception
//      ├── OrderNotFoundException.java
//      └── UserNotFoundException.java
//
// Pro: easy to find any class of a given type
// Con: changes to one feature touch many packages; most classes must be public

// ── Package-by-feature structure: ────────────────────────────────────
//
//  com.example
//  ├── order
//  │   ├── OrderController.java
//  │   ├── OrderService.java
//  │   ├── OrderRepository.java
//  │   ├── Order.java
//  │   └── OrderNotFoundException.java
//  ├── user
//  │   ├── UserController.java
//  │   ├── UserService.java
//  │   ├── UserRepository.java
//  │   ├── User.java
//  │   └── UserNotFoundException.java
//  └── common
//      ├── BaseEntity.java
//      └── ApiResponse.java
//
// Pro: all code for a feature in one place; helpers can be package-private
// Con: shared code needs a separate 'common' package

// ── Hybrid structure: ─────────────────────────────────────────────────
//
//  com.example
//  ├── order
//  │   ├── controller
//  │   │   └── OrderController.java
//  │   ├── service
//  │   │   └── OrderService.java
//  │   ├── repository
//  │   │   └── OrderRepository.java
//  │   ├── dto
//  │   │   ├── CreateOrderRequest.java
//  │   │   └── OrderResponse.java
//  │   ├── exception
//  │   │   └── OrderNotFoundException.java
//  │   └── Order.java
//  └── shared
//      ├── exception
//      │   └── BaseException.java
//      └── util
//          └── DateUtils.java

// ── Package-private for internal implementation (package-by-feature) ──
// Each top-level class goes in its OWN file (one public class per file):

// File: com/example/order/Order.java
package com.example.order;

public class Order { /* public API of the order package */ }

// File: com/example/order/OrderService.java
package com.example.order;

public class OrderService {
    private final OrderValidator validator = new OrderValidator();   // internal use
    // ...
}

// File: com/example/order/OrderValidator.java
package com.example.order;

class OrderValidator { /* invisible outside com.example.order */ }

// File: com/example/order/OrderPriceCalculator.java
package com.example.order;

class OrderPriceCalculator { /* invisible outside com.example.order */ }

Common Package Mistakes and Errors

1. Package and directory do not match. Symptoms: "cannot find symbol" or "package does not exist" at compile time, and "wrong name" or NoClassDefFoundError at runtime. Fix: make the folder path mirror the package declaration exactly, including letter case. 2. Running a packaged class by file name. java OrderService or java service/OrderService fails with "Could not find or load main class". Use the fully qualified name and a classpath: java -cp out com.example.App. 3. Forgetting -d when compiling. Without it, javac writes .class files next to the sources, which breaks the package-based layout. Use -d out. 4. Putting two public classes in one file. A file can hold only one public top-level class, and its name must match the file name. 5. Using the default package. Classes without a package declaration cannot be imported from named packages. Always declare a package in real projects. 6. Expecting import com.example.* to include sub-packages. It imports only the types directly inside com.example. Import sub-package types explicitly. 7. Making everything public. If a class is only used inside its own package, leave it package-private. A smaller public surface is easier to maintain. 8. Capital letters or underscores in package names. They compile, but they break convention and cause case-sensitivity bugs when moving between Windows/macOS and Linux file systems. 9. Splitting one package across several JARs or modules. This causes problems on the module path (Java 9+), where a package must belong to exactly one module.

Frequently Asked Questions

What is a user-defined package in Java? It is a package you create yourself to organise your own classes and interfaces, as opposed to built-in packages such as java.util or java.io. It follows exactly the same rules. How do I create a package in Java? Add a package declaration as the first statement of the file, save the file in a directory that matches the package name, and compile with javac -d so the output is generated in the matching directory structure. Does the package statement have to be the first line? It must be the first statement in the file. Comments and blank lines can come before it, and a package-info.java file may have annotations on the package declaration. Imports always come after it. Can two packages have a class with the same name? Yes. Order in com.example.model and Order in com.example.legacy are different types. If you need both in one file, import one and use the fully qualified name for the other. Do I need to import classes from the same package? No. Classes in the same package can refer to each other by simple name. What is the difference between package-private and protected? Package-private (no modifier) allows access only from the same package. Protected allows the same package plus subclasses in other packages. Does import com.example.* import sub-packages? No. It imports only the classes directly in com.example, not those in com.example.model or any other sub-package. Is the folder structure mandatory? The JVM and the standard classpath mechanism look for classes in folders that mirror their packages, and Maven and Gradle require the layout. In practice, treat it as mandatory. Should I use package-by-layer or package-by-feature? Small projects work fine with layers. As a codebase grows, package-by-feature (or the hybrid) keeps related code together and lets you hide internals with package-private access.

Key Takeaways

- The package statement comes first in the file; the folder path mirrors the package name. - Compile with javac -d out and run with java -cp out fully.qualified.ClassName; the class must have a main method. - Package-private (no modifier) is the tool for hiding internal classes and helpers. - Import statements never include sub-packages automatically. - Use lowercase, reversed-domain names, and avoid the default package. - Prefer package-by-feature or the hybrid layout as projects grow, and avoid circular dependencies between packages. - Related topics: Built-in Packages, Access Modifiers, Import Statements, Java Modules (JPMS).

Related Topics in Packages and Access Control

Package Concept
A package in Java is a namespace that groups related classes, interfaces, enums, and annotations into a logical unit. Packages solve three fundamental problems in large software systems: name collision (two classes can have the same simple name if they are in different packages), access control (package-private visibility limits access to the same package), and organisation (related types live together and can be found intuitively). Every Java class belongs to a package — either explicitly declared or the unnamed default package.
Built-in Packages
Java ships with a rich standard library organised into packages under the java and javax namespaces. These built-in packages provide data structures, I/O, networking, concurrency, database access, XML processing, GUI components, and much more. Knowing which package contains which functionality, and understanding the most important classes in the most frequently used packages, is foundational knowledge for every Java developer.
import
The import statement allows you to use a class, interface, or enum by its simple name rather than its fully qualified name. Without an import, every reference to java.util.ArrayList requires writing the full name java.util.ArrayList. With import java.util.ArrayList, you write simply ArrayList. Imports are a compile-time convenience — they have no effect on performance, do not load classes, and are not present in the compiled bytecode. The compiler uses them only to resolve simple names to fully qualified names.
Static Import
Static import allows you to access static members — static fields and static methods — of a class by their simple name without qualifying them with the class name. Introduced in Java 5, static import reduces verbosity when a few specific static members are used repeatedly. It is most useful for mathematical constants and functions, assertion methods in tests, and constants. Like regular imports, static imports are a compile-time convenience with no runtime effect.