Packages, Nested Classes, and Access Control in Java

As projects grow in size and complexity, organizing code becomes crucial for maintainability and readability. Imagine working on a project with hundreds of classes — having all these classes in a single folder would lead to chaos. Packages provide a way to group related classes and interfaces, making code organization intuitive and modular. Along with packages, access control mechanisms ensure that different parts of the code can only interact in controlled ways, promoting encapsulation and reducing unintended dependencies.
By properly using packages and access modifiers, you can achieve:
Better Code Organization: Grouping related classes into logical units.
Encapsulation: Restricting access to class members (fields and methods) to protect internal states.
Name Conflict Resolution: Classes in different packages can have the same name without collisions.
Modularity: Easier maintenance and scalability by isolating different components of a project.
Creating and Using Packages
In Java, a package is a namespace that organizes a set of related classes and interfaces. Packages are analogous to folders or directories in a file system.
Creating a Package
To declare a package, use the package
keyword at the top of a Java file:
package com.example.animals;
public class Dog {
public void bark() {
System.out.println("The dog barks");
}
}
Directory Structure
The package name corresponds to the directory structure. For the package com.example.animals
, the file structure would look like this:
src/
└── com/
└── example/
└── animals/
└── Dog.java
Compiling and Running Code with Packages
Compilation:
Navigate to the
src
directory and compile theDog.java
file:javac com/example/animals/Dog.java
Execution:
To run the
Dog
class, specify the fully qualified name (package + class):java com.example.animals.Dog
Importing Classes from Packages
To use a class from a package in another class, you need to import it using the import
statement:
import com.example.animals.Dog;
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog();
myDog.bark();
}
}
Alternatively, you can import all classes from a package using a wildcard (*
):
import com.example.animals.*;
Fully Qualified Name
You can also use the fully qualified name of a class without importing it:
public class Main {
public static void main(String[] args) {
com.example.animals.Dog myDog = new com.example.animals.Dog();
myDog.bark();
}
}
Access Modifiers
Java provides four levels of access control for class members (fields and methods) and classes:
public
: Accessible from anywhere.protected
: Accessible within the same package and by subclasses.Default (Package-Private): Accessible within the same package. No keyword is used.
private
: Accessible only within the class.
Access Modifiers for Classes
public
classes can be accessed from any other package.Default (package-private) classes can only be accessed within the same package.
Example:
// File: com/example/animals/Dog.java
package com.example.animals;
public class Dog {
public void bark() {
System.out.println("The dog barks");
}
}
// File: com/example/Main.java
package com.example;
import com.example.animals.Dog;
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog();
myDog.bark(); // Allowed since Dog is public
}
}
Access Modifiers for Class Members
Access Modifier | Within Class | Within Package | Subclasses | Outside Package |
public | ✅ | ✅ | ✅ | ✅ |
protected | ✅ | ✅ | ✅ | ❌ (unless subclass) |
Default | ✅ | ✅ | ❌ | ❌ |
private | ✅ | ❌ | ❌ | ❌ |
Example of Different Access Modifiers
// File: com/example/animals/Animal.java
package com.example.animals;
public class Animal {
public String name = "Generic Animal";
protected int age = 5;
String type = "Mammal"; // Default access
private String secret = "Hidden Info";
public void displayPublic() {
System.out.println("Public method");
}
protected void displayProtected() {
System.out.println("Protected method");
}
void displayDefault() {
System.out.println("Default method");
}
private void displayPrivate() {
System.out.println("Private method");
}
}
// File: com/example/Main.java
package com.example;
import com.example.animals.Animal;
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal.name); // Public - Accessible
// System.out.println(animal.age); // Protected - Not accessible (unless subclass)
// System.out.println(animal.type); // Default - Not accessible (different package)
// System.out.println(animal.secret); // Private - Not accessible
animal.displayPublic(); // Allowed
// animal.displayProtected(); // Not allowed
// animal.displayDefault(); // Not allowed
// animal.displayPrivate(); // Not allowed
}
}
Practical Example: Multi-Package Project with Controlled Access
Let's create a simple project with multiple packages and demonstrate access control.
Project Structure:
src/
├── com/
│ └── example/
│ └── animals/
│ └── Dog.java
│ └── zoo/
│ └── ZooKeeper.java
└── Main.java
1. Dog.java
// File: com/example/animals/Dog.java
package com.example.animals;
public class Dog {
public void bark() {
System.out.println("The dog barks");
}
}
2. ZooKeeper.java
// File: com/example/zoo/ZooKeeper.java
package com.example.zoo;
import com.example.animals.Dog;
public class ZooKeeper {
public void manageDog() {
Dog dog = new Dog();
dog.bark();
}
}
3. Main.java
// File: Main.java
import com.example.zoo.ZooKeeper;
public class Main {
public static void main(String[] args) {
ZooKeeper keeper = new ZooKeeper();
keeper.manageDog();
}
}
Compiling and Running the Project
Compile all the files:
javac com/example/animals/Dog.java com/example/zoo/ZooKeeper.java Main.java
Run the
Main
class:java Main
Output:
The dog barks
What Are Nested Classes in Java?
A nested class is a class defined within another class. Java’s nesting mechanism helps you:
Logically group related classes: If a class is only used by one other class, nesting it inside its consumer makes the relationship clearer.
Improve encapsulation: You can hide a nested class from external use if it’s not needed outside the containing class.
Reduce code clutter: By keeping minor, helper, or utility classes within the scope where they’re used, you prevent them from polluting the global namespace.
Major Categories of Nested Classes
Static Nested Classes
Inner Classes (non-static nested classes)
Inner Classes can be further divided into:
Member Inner Classes
Local Classes
Anonymous Classes
The diagram below provides a high-level summary:
Nested Classes
/ \
/ \
Static Nested Inner Classes
/ | \
/ | \
Member Inner Local Anonymous
1. Static Nested Classes
Definition
A static nested class is declared as a static
member of its enclosing (outer) class. Because it’s declared static, it doesn’t require an instance of the outer class to be instantiated.
Key Characteristics
No direct access to non-static members: A static nested class can only directly access the static members (fields, methods) of the outer class.
Behaves like a top-level class (with some limitations): From the perspective of instantiation and usage, a static nested class is somewhat similar to a top-level class, except it’s physically defined inside another class for packaging convenience and logical grouping.
Access modifiers: Like a regular class, a static nested class can have any access modifier (
public
,protected
,default
,private
).
Syntax Example
public class OuterClass {
static int outerStaticVar = 10;
int outerInstanceVar = 20;
// Static Nested Class
public static class StaticNestedClass {
void display() {
// Can access static members of OuterClass directly
System.out.println("Outer static variable: " + outerStaticVar);
// Cannot access non-static members without an instance of OuterClass
// System.out.println(outerInstanceVar); // This would be invalid
}
}
}
public class Main {
public static void main(String[] args) {
// Instantiating Static Nested Class
OuterClass.StaticNestedClass nested = new OuterClass.StaticNestedClass();
nested.display();
}
}
When to Use a Static Nested Class
Use a static nested class when the nested class does not need to access any instance (non-static) members of the outer class.
They help logically group classes without requiring a reference to the enclosing class.
2. Inner Classes (Non-Static Nested Classes)
Definition
An inner class (also known as a non-static nested class) is a nested class that is not declared as static
. It has an implicit reference to an instance of its outer class.
Key Characteristics of Inner Classes
Can access all members of the outer class, including
private
members.Requires an instance of the outer class to be created.
Maintains an implicit reference to the containing (enclosing) instance of the outer class.
Types of Inner Classes
Member Inner Classes
Local Classes
Anonymous Classes
a. Member Inner Classes
Definition
A member inner class is declared directly inside a class, at the same level as methods, constructors, or fields.
Characteristics
Instantiated using the syntax:
```java OuterClass outer = new OuterClass(); OuterClass.MemberInnerClass inner = outer.new MemberInnerClass(); ```Has access to both static and instance (non-static) members of the outer class.
The outer class instance is tied to the creation of the inner class instance. Every new instance of the inner class is associated with a specific instance of the outer class.
Syntax Example
public class OuterClass {
private int outerVar = 30;
// Member Inner Class
public class MemberInnerClass {
void display() {
// Accessing a private member of OuterClass
System.out.println("Outer variable: " + outerVar);
}
}
}
public class Main {
public static void main(String[] args) {
// Create an instance of OuterClass
OuterClass outer = new OuterClass();
// Create an instance of the Member Inner Class
OuterClass.MemberInnerClass inner = outer.new MemberInnerClass();
inner.display();
}
}
When to Use a Member Inner Class
When you need a class that is closely coupled with the outer class.
When you want to access or modify the instance state of the outer class from the nested class.
When you want to encapsulate functionality in a subcomponent but still keep it tied to the outer class’s state or methods.
b. Local Classes
Definition
A local class is a class defined within a block of code, such as inside a method, a constructor, or an initializer block.
Characteristics
Scope: A local class is only visible within the block of code where it is defined.
Access: It can access effectively final or final local variables from the enclosing scope (in Java 8 and above, local variables that are not modified after initialization are considered effectively final).
Instantiation: Can only be instantiated within that same block.
Often used for helper classes that are only relevant within one specific method or context.
Syntax Example
public class OuterClass {
void someMethod() {
final int localVar = 40; // or effectively final in modern Java
// Local Class (inside a method)
class LocalClass {
void display() {
// Can access localVar since it's final or effectively final
System.out.println("Local variable: " + localVar);
}
}
// Instantiating Local Class
LocalClass local = new LocalClass();
local.display();
}
}
public class Main {
public static void main(String[] args) {
OuterClass outer = new OuterClass();
// The class LocalClass is not visible here; it’s only accessible within someMethod()
outer.someMethod();
}
}
When to Use a Local Class
When you need a helper class inside a single method or code block and do not want it to be visible or used outside of that block.
For organizing code that is method-specific without cluttering the global namespace.
c. Anonymous Classes
Definition
An anonymous class is a local class without a name. It’s typically declared and instantiated in a single expression. This mechanism is often used to override methods of a class or to implement interfaces on the fly.
Characteristics
No explicit constructors: You define the constructor implicitly when you provide the class body.
Limited to one supertype: You can either extend one class or implement one interface, but not both.
Often used for event handling, callbacks, or one-off implementations of functional interfaces (though lambdas might be preferred in many cases now).
Syntax Example
public class OuterClass {
void createAnonymousClass() {
// Anonymous Class implementing the Runnable interface
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Running in anonymous class");
}
};
new Thread(runnable).start();
}
}
public class Main {
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.createAnonymousClass();
}
}
Another Example: Extending a Class
public class OuterClass {
void createAnonymousSubclass() {
// Anonymous class extending SomeClass
SomeClass instance = new SomeClass() {
@Override
void display() {
System.out.println("Overridden display method in anonymous subclass");
}
};
instance.display();
}
}
class SomeClass {
void display() {
System.out.println("Original display method");
}
}
When to Use Anonymous Classes
Short-lived or one-off use cases.
When you need a quick override of methods without creating a full named class.
Functional Interface usage in Java 8 and above is often done with lambda expressions instead of anonymous classes, making the code shorter and more readable. However, for classes that aren’t functional interfaces (or when you need to initialize fields, etc.), anonymous classes are still relevant.
Summary of Nested Class Types
Type | Declared as Static? | Requires Outer Instance? | Can Access Outer Instance Members? | Common Use Case |
Static Nested | Yes | No | Only static members | Grouping classes logically that do not require an outer class instance. |
Member Inner | No | Yes | Yes | When you need direct access to the outer class instance and its members. |
Local Class | No | Yes (in a block) | Yes (final/effectively final) | Helper classes restricted to a single method’s scope. |
Anonymous Class | No | Yes (in an expression) | Yes (final/effectively final) | One-off implementations (e.g., event listeners, callbacks). |
Best Practices
Access Modifiers
- Nested classes (whether static or inner) can have any access modifier:
public
,protected
,default
(package-private), orprivate
. This allows you to restrict or allow visibility as needed.
- Nested classes (whether static or inner) can have any access modifier:
Enclosing Instance Reference
Inner classes (member, local, anonymous) hold an implicit reference to an instance of the outer class. This is why they can access
private
or other instance members directly.Static nested classes have no such reference.
Serialization Concerns
Inner and anonymous classes often carry references to the outer class instance. If the outer class isn’t serializable, or if references are complicated, this can cause issues during serialization.
Ensure you understand how these references impact your object graph.
Prefer Static Nested Classes Where Possible
- If your nested class does not depend on an instance of the outer class, mark it static. This reduces unnecessary coupling and often makes the code simpler.
Use Anonymous Classes Sparingly
- With the introduction of lambda expressions in Java 8, many previous use cases for anonymous classes (especially those implementing functional interfaces) are more cleanly handled by lambdas.
Scope Limitation
- Keep classes as close to where they’re used as possible. Use local classes for method-specific operations, member inner classes for tasks that need the outer instance, and keep everything else static or top-level.
Readability and Maintainability
- Nested classes can significantly improve the readability of your code when used appropriately. They can also hide implementation details, following the Encapsulation principle.
Nested classes in Java provide a powerful mechanism to group related functionalities, control visibility, and reduce clutter. By understanding the differences between static nested classes, member inner classes, local classes, and anonymous classes, you can make more informed design decisions:
Static Nested Classes for logical grouping without needing outer instance references.
Member Inner Classes when you require a strong coupling to the outer instance state.
Local Classes for localized helper classes within a method, constructor, or block.
Anonymous Classes for quick, one-time use cases, such as event listeners or small overrides.
Subscribe to my newsletter
Read articles from Jyotiprakash Mishra directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Jyotiprakash Mishra
Jyotiprakash Mishra
I am Jyotiprakash, a deeply driven computer systems engineer, software developer, teacher, and philosopher. With a decade of professional experience, I have contributed to various cutting-edge software products in network security, mobile apps, and healthcare software at renowned companies like Oracle, Yahoo, and Epic. My academic journey has taken me to prestigious institutions such as the University of Wisconsin-Madison and BITS Pilani in India, where I consistently ranked among the top of my class. At my core, I am a computer enthusiast with a profound interest in understanding the intricacies of computer programming. My skills are not limited to application programming in Java; I have also delved deeply into computer hardware, learning about various architectures, low-level assembly programming, Linux kernel implementation, and writing device drivers. The contributions of Linus Torvalds, Ken Thompson, and Dennis Ritchie—who revolutionized the computer industry—inspire me. I believe that real contributions to computer science are made by mastering all levels of abstraction and understanding systems inside out. In addition to my professional pursuits, I am passionate about teaching and sharing knowledge. I have spent two years as a teaching assistant at UW Madison, where I taught complex concepts in operating systems, computer graphics, and data structures to both graduate and undergraduate students. Currently, I am an assistant professor at KIIT, Bhubaneswar, where I continue to teach computer science to undergraduate and graduate students. I am also working on writing a few free books on systems programming, as I believe in freely sharing knowledge to empower others.