Menu

Packages and Access Modifiers in Java

Packages and Access Modifiers

As a Java project grows from a handful of classes to hundreds, keeping related classes organised and controlling exactly who can see what becomes essential. Packages solve the organisation problem; access modifiers solve the visibility problem. This chapter covers both, since they are designed to work together.

What is a Package?

A package is simply a named folder-like grouping of related classes and interfaces, used to avoid naming conflicts and to keep a large codebase organised by purpose. Java itself ships with built-in packages, java.util for collections and utilities, java.io for input and output, and so on.

Without packages, every class in a large project would need a completely unique name, and two unrelated teams could easily clash by both naming a class Order or Logger. Packages let each team have their own Order class, distinguished by its full package path.

Creating and Importing Packages

You declare a class as belonging to a package using the package keyword as the very first line of the file, before anything else, including comments meant to be processed by tools.

// File: com/myapp/models/Student.java

package com.myapp.models;

public class Student {

String name;

}

To use a class from a different package, you import it. You can import one specific class, or use a wildcard to import every public class in that package.

// File: com/myapp/Main.java

package com.myapp;

import com.myapp.models.Student;   // import a single class

// import com.myapp.models.*;    // or import everything in the package

public class Main {

public static void main(String[] args) {

Student s = new Student();

s.name = "Anita";

}

}

Classes in the java.lang package, such as String and Math, are imported automatically by Java in every file, which is why you have never had to write import java.lang.String; even though you have used String constantly.

Access Modifiers Overview

Access modifiers control which other classes are allowed to see and use a particular field, method, constructor, or class. Java provides four levels of visibility, ranging from completely open to fully restricted.

Modifier

Same Class

Same Package

Subclass (Different Package)

Different Package, No Inheritance

public

Yes

Yes

Yes

Yes

protected

Yes

Yes

Yes

No

default (no keyword)

Yes

Yes

No

No

private

Yes

No

No

No

Notice that visibility only ever gets wider as you move down this list to up, public is the most permissive, private is the most restrictive, and the other two sit in between depending on package and inheritance relationships.

Public Access Modifier

A member marked public is visible everywhere: same class, same package, subclasses in other packages, and completely unrelated classes in other packages. This is the modifier you use for anything meant to form part of a class's external, reusable interface.

public class Vehicle {

public String brand;

public void startEngine() {

System.out.println(brand + " engine starting");

}

}

Private Access Modifier

A member marked private is visible only inside the exact same class that declares it, not even subclasses can see it directly. This is the backbone of encapsulation, covered in Chapter 4: fields are kept private and accessed only through public getters and setters.

public class BankAccount {

private double balance;   // only BankAccount itself can touch this directly

public double getBalance() {

return balance;

}

}

Protected Access Modifier

A member marked protected is visible within the same package, and also to subclasses in different packages, even though it is hidden from unrelated classes in other packages. It is a middle ground used when you want subclasses to be able to extend or use a member, without exposing it to the entire world.

package com.myapp.models;

public class Animal {

protected String name;

protected void eat() {

System.out.println(name + " is eating");

}

}

// In a different package:

package com.myapp.pets;

import com.myapp.models.Animal;

public class Dog extends Animal {

void bark() {

eat();   // allowed — Dog is a subclass of Animal

}

}

Default Access Modifier

If you write no access modifier at all, the member gets default (also called package-private) access, visible only to other classes within the exact same package, with no special treatment for subclasses in other packages.

package com.myapp.utils;

class Helper {       // default access — only visible inside com.myapp.utils

void assist() {   // default access method

System.out.println("Helping...");

}

}

This is the access level you get automatically and is well suited to internal helper classes that a package needs for its own use, but that should never be part of the package's public-facing interface.

Package Structure Best Practices

• Use reverse domain naming for top-level packages, such as com.companyname.projectname, to keep names globally unique.

• Group classes by feature or layer, for example separating com.myapp.models, com.myapp.services, and com.myapp.controllers, rather than dumping every class into one package.

• Keep package names entirely lowercase, by convention, to visually distinguish them from class names, which start with an uppercase letter.

• Default to the most restrictive access modifier that still works, prefer private, then default, then protected, and reach for public only when a member genuinely needs to be part of the class's external contract.

Did You Know?

The directory structure on disk must mirror the package declaration exactly. A class declared as package com.myapp.models; must physically live inside a folder path com/myapp/models/, or the Java compiler will refuse to find it.