☕ Java

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.

Why Packages Exist

As software systems grow, the number of classes grows with them. Without a grouping mechanism, tens of thousands of class names compete in a single flat namespace. Two developers independently name a class User, a class Logger, or a class Config — without packages, only one can exist. Packages solve this by creating hierarchical namespaces: com.example.auth.User and com.example.billing.User are distinct types that can coexist in the same program. The naming analogy to file systems is intentional and literal. A package corresponds to a directory. A class in package com.example.service corresponds to a .class file in the directory com/example/service/. The JVM and compiler use this correspondence to locate class files: when code references com.example.service.OrderService, the runtime looks for the file com/example/service/OrderService.class relative to each entry on the classpath. This physical correspondence is not just a convention — it is enforced by the toolchain. Packages also define one of Java's four access levels. Package-private access (the default when no modifier is written) restricts visibility to classes in the same package. A class, field, or method with package-private access is visible to all code in the same package but completely invisible to code in any other package. This is a powerful encapsulation tool: implementation details that are shared within a subsystem but should not be exposed externally are given package-private access, making the package itself a unit of encapsulation beyond the individual class. The package hierarchy does not imply inheritance or special access relationships between nested packages. java.util and java.util.concurrent are entirely separate packages — code in java.util.concurrent has no special access to package-private members of java.util. Each level of the dotted package name is a separate namespace with its own access boundary. The hierarchy is purely organisational and for readability — it does not affect the access control semantics.
Java
// ── Package declaration — first statement in a Java source file: ──────
package com.example.service;   // must be the FIRST statement, before imports

// ── Package establishes the fully qualified class name: ───────────────
// File: src/com/example/service/OrderService.java
// Package: com.example.service
// Simple name: OrderService
// Fully qualified name: com.example.service.OrderService

// ── Two classes with the same simple name in different packages: ───────
// com.example.auth.User        — authentication user
// com.example.billing.User     — billing customer
// Both can exist simultaneously — packages prevent collision.

// ── Package-private access — default when no modifier: ────────────────
package com.example.internal;

class InternalHelper {          // no modifier = package-private
    static String format(String s) { return s.trim(); }
}

class PublicService {           // this is also package-private (no public)
    public void serve() {
        InternalHelper.format("data");   // ✓ same package
    }
}

// From a different package — InternalHelper is INVISIBLE:
// com.example.other.SomeClass cannot reference com.example.internal.InternalHelper
// even with a full importpackage-private means invisible outside the package.

// ── Physical directory structure mirrors package: ─────────────────────
//
//  src/
//  └── com/
//      └── example/
//          ├── service/
//          │   ├── OrderService.java    (package com.example.service)
//          │   └── UserService.java     (package com.example.service)
//          ├── model/
//          │   ├── Order.java           (package com.example.model)
//          │   └── User.java            (package com.example.model)
//          └── repository/
//              ├── OrderRepository.java (package com.example.repository)
//              └── UserRepository.java  (package com.example.repository)

Package Naming Conventions

Java package names follow a universally adopted reverse-domain convention. Your organisation's domain name is reversed and used as the root of all your packages. If your domain is example.com, your packages begin with com.example. If your domain is myorg.co.uk, your packages begin with uk.co.myorg. This convention almost completely eliminates name collisions between packages from different organisations because domain names are globally unique. Below the reversed domain, the package hierarchy reflects the structure of your code. Typical top-level divisions are by function (service, model, repository, controller), by feature (auth, orders, billing, notifications), or by a combination of both. There is no universally correct structure — the goal is that the package of a class tells you meaningfully what role the class plays in the system. Package names are conventionally all lowercase with no underscores or hyphens. Digits are allowed but not as the first character of a segment. Numbers in domain names or abbreviations in package names follow the same lowercase rule. The com, org, net, and edu prefixes come from domain name conventions and are not meaningful beyond their use as package namespace roots. The unnamed default package — placing a class in no package — is permitted but strongly discouraged for anything other than throwaway test code and the smallest scripts. Classes in the unnamed default package cannot be imported from named packages. They cannot be referenced by fully qualified name. They are invisible to the entire ecosystem of tools that expect named packages. All production code should declare an explicit package.
Java
// ── Package naming convention — reverse domain + structure: ──────────
//
// Organisation domain: example.com
// Reversed:            com.example
//
// Application packages:
//   com.example.model          — domain model classes
//   com.example.service        — business logic
//   com.example.repository     — data access
//   com.example.controller     — web layer (REST/MVC)
//   com.example.config         — configuration
//   com.example.util           — utilities
//   com.example.exception      — custom exceptions
//   com.example.dto            — data transfer objects
//
// By feature (alternative):
//   com.example.order          — order feature: Order, OrderService, etc.
//   com.example.user           — user feature: User, UserService, etc.
//   com.example.payment        — payment feature

// ── Naming rules: ─────────────────────────────────────────────────────
// ✓ All lowercase:          com.example.service
// ✗ Uppercase letters:      com.Example.Service      — wrong convention
// ✓ Only letters, digits:   com.example.api2
// ✗ Hyphens:                com.example.my-service   — invalid identifier
// ✗ Starting with digit:    com.example.2service     — invalid identifier
// ✓ Underscore ok but rare: com.example.my_service   — legal but rare

// ── Reserved word in package name — underscore prefix: ────────────────
// If a package segment is a reserved word (like 'int', 'class'):
package com.example.int_;      // convention: append underscore
package com.example._new;      // or prefix with underscore

// ── Default (unnamed) package — avoid in production: ─────────────────
// HelloWorld.java with NO package declaration:
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello");
    }
}
// This class is in the unnamed default package.
// No other named package can import or reference it.
// Use only for simple experiments — never in production code.

Package and Access Modifiers

Java has four access levels and packages are central to three of them. Public members are accessible from any class in any package. Protected members are accessible within the same package and from subclasses in any package. Package-private (no modifier) members are accessible only within the same package. Private members are accessible only within the declaring class. Package-private is the most overlooked and underused access level. Many developers write public or private for everything, skipping the package-private option. But package-private is a powerful design tool: it lets classes within the same package share implementation details while keeping those details invisible to the outside world. A set of collaborating classes in a package can expose rich internal APIs to each other while presenting only a minimal public API to the rest of the system. The package boundary becomes an encapsulation boundary. This is the original intent of packages in Java's design — packages are not just directories, they are units of encapsulation. A well-designed package has a small set of public types that form its API, and a larger set of package-private types that are the implementation. Code outside the package cannot see the implementation, only the API. This is stronger encapsulation than is achievable at the class level alone. The module system introduced in Java 9 (JPMS — Java Platform Module System) takes this concept further. Modules declare which packages they export — a package can be internal to a module and not exported, making it invisible even to other packages in other modules, not just other packages in the same build. But package-private access is the tool available without modules.
Java
// ── Access levels and package boundaries: ────────────────────────────
//
// package com.example.order (four classes):

// PUBLIC — visible everywhere:
public class Order { ... }

// PACKAGE-PRIVATE — visible only within com.example.order:
class OrderValidator { ... }       // no modifier
class OrderRepository { ... }      // no modifier

// PROTECTED — visible in package + subclasses:
public class BaseOrderProcessor {
    protected void processStep() { ... }  // subclasses can override
}

// ── Package-private as an encapsulation tool: ─────────────────────────
// Package: com.example.auth
// Public API: AuthService (what the rest of the app uses)
// Internal implementation: TokenStore, SessionCache, HashUtils (hidden)

// AuthService.java:
package com.example.auth;

public class AuthService {
    private final TokenStore    tokenStore    = new TokenStore();
    private final SessionCache  sessionCache  = new SessionCache();

    public String login(String username, String password) {
        String hash = HashUtils.hash(password);   // package-private
        // ...
        return tokenStore.createToken(username);
    }
}

// TokenStore.java:
package com.example.auth;

class TokenStore {                 // package-private — invisible outside auth
    String createToken(String username) { ... }
    boolean validate(String token) { ... }
}

// HashUtils.java:
package com.example.auth;

class HashUtils {                  // package-private — invisible outside auth
    static String hash(String input) { ... }
}

// From com.example.service.OrderService — cannot access internal types:
// import com.example.auth.TokenStore;   // COMPILE ERROR — package-private
// import com.example.auth.HashUtils;    // COMPILE ERROR — package-private
// Only com.example.auth.AuthService is accessible (it's public).

Related Topics in Packages and Access Control

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.
User-defined Packages
User-defined packages are packages you create to organise your own application's classes and interfaces. They follow the same rules as Java's built-in packages — the package declaration must be the first statement in the file, the directory structure must match the package hierarchy, and access modifiers control visibility across package boundaries. Designing a meaningful package structure is a foundational software engineering skill that directly affects how maintainable and navigable a codebase remains as it grows.
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.