Access Modifiers in Java: public, private, protected Explained
Access modifiers in Java form the backbone of how I control visibility and accessibility of classes, methods, and variables. These modifiers are essential in crafting secure and well-organized code because they define the scope in which members of a class can be accessed. If you don’t manage access properly, your code quickly becomes difficult to maintain, debug, and extend.
In this article, I’ll walk you through the key access modifiers in Java: public, private, and protected. We’ll explore what each modifier means, how they affect accessibility, and when to use them in your coding projects. Drawing from my own experience, I’ll also share practical examples and tips to help you confidently apply access modifiers in your Java code.
What Are Access Modifiers in Java?
Access modifiers in Java specify the level of access control for classes, methods, constructors, and variables. They help me encapsulate the data and protect the internal workings of a class from unintended interference or misuse by other parts of the program.
Java has four types of access levels:
- public
- private
- protected
- (default) package-private (when no modifier is specified)
Each controls visibility differently, allowing me to enforce strict or relaxed access rules depending on design needs.
Public Access Modifier
The public
access modifier is the most permissive. When I declare a class member as public, it means it can be accessed from any other class anywhere whether inside the same package or from another package.
When I Use Public
I use public
for parts of my code that are meant to be accessible everywhere, such as:
- Public APIs
- Methods that define the behavior of a class to outside users
- Constants or utility methods in utility classes
Example of Public Modifier
java public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
The add
method can be called from anywhere in the program, whether it’s from another class in the same package or a different package.
Public Classes and Interfaces
Classes and interfaces can also be declared as public. When a class is public, it must be in a file with the same name. Public classes serve as entry points or core components exposed for use.
java public class MainApplication {
public static void main(String[] args) {
System.out.println("Welcome to the application!");
}
}
Things to Keep in Mind with Public
While public members provide flexibility and accessibility, exposing too many members as public can break encapsulation. I try to restrict access to only what needs to be public to protect the internal state of the class.
Private Access Modifier
The private
modifier is the strictest form of access control. When a member is declared private, it is only accessible within the class it is declared in. This means no other class, not even subclasses, can access private members directly.
When I Use Private
I use private
to:
- Hide implementation details that shouldn’t be exposed
- Protect sensitive data or state within an object
- Control access through getter and setter methods instead of allowing direct field access
Example of Private Modifier
java public class BankAccount {
private double balance;
public double getBalance() {
return balance;
}
public void deposit(double amount) {
if(amount > 0) {
balance += amount;
}
}
}
Here, the balance
variable is private, so it can’t be changed directly from outside the class. Instead, I expose controlled access through the deposit
method and getBalance
getter.
Private Constructors
I sometimes use private constructors to implement design patterns like Singleton, ensuring no other class can instantiate the class directly.
java public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
Private Methods
Private methods help me organize and reuse internal logic within a class without exposing those details externally.
java public class EmailService {
public void sendEmail(String recipient) {
if(validateEmail(recipient)) {
System.out.println("Sending email to " + recipient);
}
}
private boolean validateEmail(String email) {
return email.contains("@");
}
}
The validateEmail
method is private because it’s only relevant inside the class and shouldn’t be accessed by outside code.
Protected Access Modifier
The protected
modifier strikes a balance between public and private. A protected member is accessible within the same package and also accessible to subclasses even if they are in different packages.
When I Use Protected
I use protected when I want subclasses to have access to certain members to extend or customize behavior, but I don’t want to expose those members to the entire world.
Example of Protected Modifier
java public class Animal {
protected String name;
protected void eat() {
System.out.println(name + " is eating");
}
}
public class Dog extends Animal {
public void bark() {
System.out.println(name + " is barking");
}
}
Here, name
and eat
are protected, so the subclass Dog
can access and use them directly.
Protected vs Package-Private
If a member is declared with no access modifier (default), it is accessible only within the same package. Protected extends this accessibility to subclasses outside the package.
Package-Private (Default) Access
If I declare a class member without any explicit access modifier, it gets package-private access by default. This means the member is accessible only to other classes in the same package.
This default behavior is useful when I want to restrict access to related classes grouped in the same package but hide from others.
java class Helper {
void assist() {
System.out.println("Assisting...");
}
}
assist()
here is package-private.
Access Modifiers and Classes
Classes themselves can be declared as either public or package-private (default). Other modifiers like private or protected cannot be applied to top-level classes.
- Public classes are accessible anywhere.
- Package-private classes are accessible only within their own package.
I use package-private classes for internal helpers or implementation details not meant to be part of the public API.
How I Decide Which Access Modifier to Use
Deciding the correct access level for members is one of the most important design decisions I make when coding in Java.
Here’s my approach:
Start Restrictive
I always start with the most restrictive access (private
) and open up only when necessary. This helps me avoid accidental misuse of class internals.
Expose Only What Is Necessary
I expose methods or fields as public only if they form part of the intended interface of the class. Everything else remains private or protected.
Use Protected for Extensibility
When designing classes for inheritance, I use protected members to provide hooks or utilities for subclasses.
Package-Private for Internal Collaboration
For classes and methods that work closely within the same package, I leave them package-private to avoid polluting the public API.
Practical Examples and Use Cases
Encapsulation with Private Fields
java public class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
if(username != null && !username.isEmpty()) {
this.username = username;
}
}
public void setPassword(String password) {
if(password.length() >= 8) {
this.password = password;
}
}
}
This code shows how private fields combined with getters/setters help enforce validation and protect sensitive data.
Template Method Using Protected Methods
java public abstract class DataProcessor {
public void process() {
readData();
processData();
saveData();
}
protected abstract void readData();
protected abstract void processData();
protected abstract void saveData();
}
public class CsvProcessor extends DataProcessor {
@Override
protected void readData() {
System.out.println("Reading CSV data");
}
@Override
protected void processData() {
System.out.println("Processing CSV data");
}
@Override
protected void saveData() {
System.out.println("Saving CSV data");
}
}
The protected methods provide a contract for subclasses while hiding implementation details from external code.
Public APIs with Private Helpers
java public class PasswordValidator {
public boolean isValid(String password) {
return hasMinimumLength(password) && hasSpecialCharacter(password);
}
private boolean hasMinimumLength(String password) {
return password.length() >= 8;
}
private boolean hasSpecialCharacter(String password) {
return password.matches(".*[!@#$%^&*()].*");
}
}
Public method isValid
is the only access point; helpers remain private.
Common Pitfalls and How I Avoid Them
Making Everything Public
A common mistake is to make all fields and methods public for convenience. This exposes internals and makes future changes risky. I always aim for encapsulation first.
Ignoring Package-Private Level
Developers often forget about the package-private level, which is great for modular design. I use it intentionally to organize code without exposing it publicly.
Confusing Protected and Public
Sometimes protected members get treated as public, leading to tight coupling. I document intended use and limit subclassing where appropriate.
Overusing Getters and Setters
Blindly generating getters and setters for every field defeats encapsulation. I only provide accessors when really necessary and consider immutability.
Access Modifiers and Security
Access modifiers help me enforce security boundaries in my application. By limiting access to sensitive data or methods, I reduce the attack surface and make my code more robust.
Summary of Access Modifiers in Java
Modifier | Access Scope | Typical Use Case |
---|---|---|
public | Anywhere | Public APIs, constants, utilities |
private | Within the same class only | Internal data, helper methods |
protected | Same package + subclasses in other packages | Extensible base classes, hooks for subclasses |
(default) | Same package only | Package-internal helpers, collaborators |
Final Thoughts
Access modifiers in Java give me fine-grained control over how my code interacts internally and externally. Using public, private, and protected thoughtfully allows me to build maintainable, flexible, and secure applications.
Whenever I write a new class, I start with private fields and methods and then selectively open up access as design demands. Protecting the integrity of my objects and preventing unintended interference has saved me countless hours of debugging and refactoring.
If you take away one thing from this article, it should be: restrict access by default, expose intentionally, and think carefully about who should use each part of your code.
Applying access modifiers correctly is one of the foundational skills every Java developer must master for clean, professional coding.