Core Java · Lesson 8
Access Modifiers
Controlling Access with Modifiers
Access modifiers control where code can access a class, method, or field from. Java provides four levels: public (accessible everywhere), protected (accessible in same package and subclasses), package-private (accessible only in same package, the default), and private (accessible only in the same class). Proper use of access modifiers is fundamental to encapsulation and prevents other code from misusing your classes.
Public: Accessible from Anywhere
The public modifier means any code anywhere can access the class, method, or field. Use public for your main API — the interfaces others depend on. A public class must be in its own file with the same name. Every public class, method, and field is part of your code's contract; changing them later breaks other code relying on them.
// File: Calculator.java
public class Calculator {
// Public method: anyone can call this
public int add(int a, int b) {
return a + b;
}
public int multiply(int a, int b) {
return a * b;
}
}
// File: MyApp.java
public class MyApp {
public static void main(String[] args) {
Calculator calc = new Calculator();
int result = calc.add(5, 3);
System.out.println(result); // 8 - works fine
}
}
Private: Hidden from Outside Access
The private modifier is the most restrictive — only the class itself can access it. Use private for implementation details you don't want others using directly. Private methods and fields give you freedom to change internal logic without breaking other code. This is the core of encapsulation.
public class BankAccount {
private double balance; // Private: others can't access directly
private String accountNumber;
public BankAccount(String accountNumber, double initialBalance) {
this.accountNumber = accountNumber;
this.balance = initialBalance;
}
// Private method: internal helper, not part of public API
private void validateAmount(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
}
// Public method: controlled access to balance
public double getBalance() {
return balance;
}
public void deposit(double amount) {
validateAmount(amount); // Call private helper
balance += amount;
}
public void withdraw(double amount) {
validateAmount(amount);
if (amount > balance) {
throw new IllegalArgumentException("Insufficient funds");
}
balance -= amount;
}
}
Protected: For Subclasses and Same Package
The protected modifier allows access from the same package and from any subclass, even if the subclass is in a different package. Use protected for methods and fields meant for subclasses to override or use. It's less restrictive than private but more controlled than public.
public class Animal {
protected String name; // Protected: subclasses can access
protected int age;
protected void makeSound() { // Protected: subclasses can override
System.out.println(name + " makes a sound");
}
}
public class Dog extends Animal {
public Dog(String name, int age) {
this.name = name; // Can access protected from parent
this.age = age;
}
@Override
protected void makeSound() { // Override protected method
System.out.println(name + " barks");
}
}
Package-Private: Default Access Within a Package
When you don't specify any modifier (the default), a class or member is package-private — accessible only within its package. This is useful for internal classes you don't want exposed publicly. Use package-private for implementation details within a package.
// File: com/example/database/Database.java
package com.example.database;
// No modifier = package-private, can't be used outside this package
class DatabaseConnection { // package-private class
void connect() {
System.out.println("Connecting to database");
}
}
// File: com/example/database/UserRepository.java
package com.example.database;
public class UserRepository { // public class
private DatabaseConnection connection; // package-private class
void getUser(int id) { // package-private method
connection.connect(); // Can access within package
}
}
// File: com/example/Main.java
package com.example;
public class Main {
public static void main(String[] args) {
// UserRepository repo = new UserRepository(); // OK: public class
// DatabaseConnection conn = new DatabaseConnection(); // ERROR: not accessible
}
}
| Modifier | Same Class | Same Package | Subclass (Different Package) | Outside Package |
|---|---|---|---|---|
private | ✓ | ✗ | ✗ | ✗ |
| (package-private) | ✓ | ✓ | ✗ | ✗ |
protected | ✓ | ✓ | ✓ | ✗ |
public | ✓ | ✓ | ✓ | ✓ |
Access Modifier Rules and Restrictions
Every class member must have an access level. For classes, you can use only public or package-private (default). For methods and fields, you can use all four. Nested classes can use all four. A public class's public methods cannot return or accept private inner classes as parameters — that would violate access rules. Plan your API carefully: exposing something as public is hard to take back.
public class APIDesign {
// Good: private implementation class
private static class InternalProcessor {
void process() { }
}
// Good: public interface/method is the contract
public void doWork() {
InternalProcessor processor = new InternalProcessor();
processor.process();
}
}
// Client code sees only the public method, not the internal implementation
class ClientCode {
public static void main(String[] args) {
APIDesign api = new APIDesign();
api.doWork(); // Only this is accessible
}
}
Common Mistake: Making everything public for convenience. It seems easier now ("they might need it"), but pollutes your API and locks you into backward compatibility. Future changes risk breaking client code. Be conservative: start
private, expose asprotectedorpubliconly when needed. You can always loosen restrictions later, but tightening them breaks existing code.
Best Practices for Access Modifiers
- Make fields
privateby default: expose through getters/setters only if needed - Make methods
publiconly if they're part of your intended API - Use
protectedfor methods subclasses should override: not for random access - Prefer package-private for internal implementation classes: keeps public API small
- A public class should have few public members: reduces coupling and surface area
- Follow the principle of least privilege: give minimum access needed
- Document why access levels were chosen: helps maintainers understand intent
Real-World Example: E-Commerce Library
A payment processing library has a public PaymentProcessor class with a public processPayment() method as its API. Internally, it uses package-private PaymentValidator, EncryptionHandler, and DatabaseConnector classes hidden from users. Fields like apiKey and database are private with no getters — completely hidden. Protected methods in a public abstract class PaymentGateway allow payment providers (customers' subclasses) to implement their own gateways. By carefully controlling visibility, the library's authors can refactor internals, fix bugs, and optimize without breaking client code that depends only on the public API.
Quiz Check