Back to Blog
Java

Java Package: Namespace and Access Control

java package: Learn how Java packages organize classes, control access, and avoid naming conflicts, with practical examples of declaration, import, and structure.

packageimportaccess modifiersnamespacemodule system
Diagram showing how Java packages group classes and control access with public and package-private visibility.

A Java package is a namespace that groups related classes and interfaces. It gives you a way to organize code, avoid name collisions, and control visibility through access modifiers. When you write package com.example.myapp; at the top of a source file, you are telling the compiler that every type declared in that file belongs to that namespace.

What a Java Package Provides

Packages serve three main purposes. First, they prevent naming conflicts. Two different libraries can both define a User class, but if they live in different packages, com.example.auth.User and org.other.app.User can coexist without ambiguity. Second, packages give you a mechanism for access control. A class or member marked public is accessible from any other package, while a member with no modifier is accessible only within its own package. Third, packages make large codebases easier to navigate because they group related functionality into a logical structure.

The Java runtime also uses packages to locate classes. The fully qualified name of a class, such as java.util.List, includes the package name. When you import a type, you are telling the compiler to resolve the short name List to java.util.List for the current compilation unit.

Declaring a Package

The package statement must be the first statement in a source file, before any imports or class declarations. Only one package statement is allowed per file, and it applies to all types in that file. Here is a minimal example:

package com.example.myapp; public class Main { public static void main(String[] args) { System.out.println("Hello from the myapp package"); } }

If you omit the package statement, the class goes into the default package, which has no name. The default package is convenient for small scripts, but it is not suitable for real applications because you cannot import types from it in a normal way, and it makes access control meaningless. Every production codebase should declare an explicit package.

Naming Conventions and Reverse-Domain Names

Package names are typically written in all lowercase, and the convention is to use your organization's reverse domain name as the root. For example, if your company owns example.com, you might start all packages with com.example. This practice reduces the chance of collisions with packages from other organizations. The Java Language Specification recommends avoiding reserved words and using only ASCII letters and digits, with underscores allowed but discouraged.

A package name is a sequence of identifiers separated by dots. Each identifier must be a valid Java identifier. The full package name maps directly to a directory structure on the filesystem. com.example.myapp corresponds to the directory com/example/myapp. When you compile with javac, the class files are placed in that directory structure, and the Java runtime uses the same structure to locate them on the classpath.

Importing Types from Other Packages

To use a class from another package, you can refer to it by its fully qualified name every time, or you can use an import statement. Imports are written after the package statement and before the class declaration. There are two forms: single-type imports and on-demand imports.

package com.example.myapp; import java.util.List; import java.util.ArrayList; public class Example { List<String> items = new ArrayList<>(); }

The single-type import above brings List and ArrayList into scope. You can also use an on-demand import with a wildcard: import java.util.*; which imports all public types from that package. On-demand imports do not make the code slower; the compiler resolves the actual type at compile time. However, they can make it harder to see which types are actually used, so many style guides prefer explicit single-type imports.

A common mistake is to think that importing a package imports its subpackages. Importing java.util.* does not give you access to java.util.concurrent.*. Each package is independent, and you must import each one separately.

Access Control Across Packages

Access modifiers determine how visible a type or member is from other packages. There are four levels, and the package boundary is central to three of them:

  • public: accessible from any package.
  • protected: accessible from the same package and subclasses in other packages.
  • no modifier (package-private): accessible only from the same package.
  • private: accessible only within the same class.

Consider a class with a package-private field:

package com.example.data; public class Record { int id; // package-private public String name; }

Another class in the same package can access id, but a class in a different package cannot. This is useful when you want to expose internal state only to closely related classes that share the package, such as helper classes or builders. The protected modifier is more subtle: it allows access from subclasses even if they are in a different package, but only through an instance of the subclass. Direct access to a protected member on an instance of the parent class is not allowed from another package.

Package Structure and Compilation

The directory layout of your source files should mirror the package structure. The javac compiler expects to find source files in directories that correspond to their package names. When you compile, you typically set the source root and the output directory. For example, if your source root is src, a file with package com.example.myapp; must live at src/com/example/myapp/Main.java. If you place it in the wrong directory, compilation fails with an error.

The classpath is a list of directories and JAR files that the compiler and runtime use to find classes. When you run a program with java -cp classes com.example.myapp.Main, the runtime looks for classes/com/example/myapp/Main.class. The same rule applies to JAR files, which are just ZIP archives with the package directory structure inside. Keeping the package structure consistent is essential for both compilation and deployment.

Packages and the Java Module System

Java 9 introduced modules, which sit on top of packages. A module is a named, self-describing collection of packages and resources, with an explicit list of which packages it exports and which modules it requires. A package is still the unit of organization, but a module can restrict which packages are accessible to other modules. For example, a module can export com.example.api but keep com.example.internal hidden. This adds a higher level of encapsulation beyond the access modifiers.

When you work with modules, the module-info.java file declares the module name and its dependencies. The package structure inside the module remains the same, but the module system enforces that only exported packages are visible to other modules. This is particularly relevant for libraries and large applications where you want to control the public API surface.

Common Mistakes When Working with Packages

One frequent error is forgetting to declare a package and then trying to import a class from the default package. The default package is not importable, so you must move the class into a named package. Another mistake is using a package name that conflicts with a standard Java package, such as java.util or java.lang. The compiler and runtime give priority to the standard packages, so your own classes in those packages may be ignored or cause errors.

A third issue is relying on package-private access across different source directories that happen to have the same package name. In a modular project, two modules cannot both export the same package, and even on the classpath, having two JARs with the same package can lead to subtle class-loading problems. Always ensure that your package names are globally unique, typically by using your reverse domain name.

Finally, do not use import to bring in classes that are in the same package. They are automatically accessible without an import. Adding redundant imports does not cause an error, but it clutters the code and can confuse readers about the actual dependencies.

java package: Practical Usage and Code Examples | RYUSLOG DEV