Access specifiers in Java
-
Last Updated: March 10, 2025
-
By: javahandson
-
Series
Learn about Access Specifiers in Java – Private, Public, Protected, and Default. Understand their usage, differences, and best practices with examples.
Access specifiers in Java are keywords that define the visibility or scope of a class, method, or variable. They control which other classes or components can access a particular class member.
1. Restricts Direct Access: Prevents direct modification of sensitive data by using private members.
2. Encapsulates Implementation Details: Changes to internal logic don’t affect other parts of the program.
3. Provides Controlled Access: Getters and setters allow controlled modifications, ensuring data consistency.
1. Reduces Code Coupling: Restricts access to only necessary parts, making code changes easier.
2. It prevents unintended modifications and limits access to sensitive fields, reducing bugs.
3. Encourages a Clear API: Developers know what is intended for external use (public) and what is internal (private).
1. Prevents Unauthorized Access: private and protected restrict access to critical data and methods.
2 Minimizes Data Corruption: Controlled access ensures only valid operations modify data.
3. It encourages Secure Coding Practices and hides sensitive implementation details, reducing vulnerabilities.
1. Allows Controlled Inheritance: Protected methods and fields can be inherited without exposing them to external classes.
2. Facilitates Code Reuse: Subclasses can reuse existing logic without modifying base class internals.
3. Enables Method Overriding: Proper access control ensures overridden methods follow the intended behavior.
1. Prevents Ambiguity: Restricts visibility of members, avoiding conflicts in large projects.
2. Encapsulates Implementation Details: Different classes can have the same variable names without interference.
3. Ensures Proper Access Scope: Methods and variables are accessible only where necessary, preventing clashes.
1. Encourages the Separation of Concerns: Access specifiers define clear boundaries between components.
2. Enhances Readability: Developers can quickly understand what’s accessible and what’s internal.
3. Facilitates Unit Testing: Private methods keep internals hidden while exposing only necessary functionality for testing.
Java provides four types of access specifiers:
Private – Accessible only within the same class.
Default (Package-private) – Accessible within the same package.
Protected – Accessible within the same package and subclasses in different packages.
Public – Accessible from anywhere in the program.
1. The most restrictive access specifier.
2. Members declared private are only accessible within the same class.
3. Other classes cannot access them, even those in the same package.
Common Use Case: Used for encapsulation, keeping fields and helper methods private.
package com.java.handson.access.specifiers.pkg1; public class Student { private int rollNumber; String name; int marks; public Student(int rollNumber, String name, int marks) { this.rollNumber = rollNumber; this.name = name; this.marks = marks; } public int getRollNumber() { return rollNumber; // private variable can be accessed in the same class } } package com.java.handson.access.specifiers.pkg1; public class Main { public static void main(String[] args) { Student student = new Student(101, "Suraj", 70); String name = student.name; int rollNumber = student.rollNumber; // Compilation Error } } Output: java: rollNumber has private access in com.java.handson.access.specifiers.pkg1.Student
If no access specifier is mentioned, Java assigns default (package-private) access. Members with default access can be accessed within the same package but not from outside.
Common Use Case: Used when classes within the same package need access, but external classes should not.
package com.java.handson.access.specifiers.pkg1; public class Main { public static void main(String[] args) { Student student = new Student(101, "Suraj", 70); String name = student.name; // name is of default type hence it can be accessed in same package System.out.println("Name of student : " + name); } } Output: Name of student : Suraj
package com.java.handson.access.specifiers.pkg2; import com.java.handson.access.specifiers.pkg1.Student; public class Main { public static void main(String[] args) { Student student = new Student(101, "Suraj", 70); String name = student.name; // Compilation Error System.out.println("Name of student : " + name); } } Output: java: name is not public in com.java.handson.access.specifiers.pkg1.Student; cannot be accessed from outside package
Members declared protected are accessible:
Common Use Case: Used when a method/variable should be accessible to child classes but not to unrelated classes.
package com.java.handson.access.specifiers.pkg1; public class Student { private int rollNumber; String name; protected int marks; public Student(int rollNumber, String name, int marks) { this.rollNumber = rollNumber; this.name = name; this.marks = marks; } } package com.java.handson.access.specifiers.pkg2; import com.java.handson.access.specifiers.pkg1.Student; public class EngineeringStudent extends Student { public EngineeringStudent(int rollNumber, String name, int marks) { super(rollNumber, name, marks); } public void displayMarks() { System.out.println("Marks: " + marks); // Allowed (protected, accessible in subclass) } } package com.java.handson.access.specifiers.pkg2; import com.java.handson.access.specifiers.pkg1.Student; public class Main { public static void main(String[] args) { EngineeringStudent student = new EngineeringStudent(101, "Suraj", 70); student.displayMarks(); } } Output: Marks: 70
The least restrictive access specifier. Members declared public are accessible from anywhere in the program.
Common Use Case: Used for methods, constructors, and classes that should be universally accessible.
package com.java.handson.access.specifiers.pkg1; public class Student { private int rollNumber; String name; protected int marks; public String mainSubject; public Student(int rollNumber, String name, int marks) { this.rollNumber = rollNumber; this.name = name; this.marks = marks; } } package com.java.handson.access.specifiers.pkg2; import com.java.handson.access.specifiers.pkg1.Student; public class Main { public static void main(String[] args) { Student student = new Student(101, "Suraj", 70); student.mainSubject = "Maths"; // Allowed (public variable can be accessed anywhere) System.out.println("Main Subject: " + student.mainSubject); } } Output: Main Subject: Maths
Access specifiers in Java apply to classes, variables, methods, and constructors, but their behavior differs based on context. Below is a breakdown of how they work in different scenarios.
Access specifiers can be applied to top-level and inner classes.
A top-level class can only have public or default (package-private) access.
Access Specifier | Allowed in Top-Level Class? | Behavior |
public | Yes | Accessible from anywhere. |
default | Yes | Accessible only within the same package. |
private | No | Not allowed for top-level classes. |
protected | No | Not allowed for top-level classes. |
For inner classes (classes inside another class), all four access specifiers are allowed.
public class Outer { private class PrivateInner { } // Only accessible within Outer class DefaultInner { } // Accessible within the same package protected class ProtectedInner { } // Accessible within package and subclasses public class PublicInner { } // Accessible everywhere }
Variables (fields) can use all four access specifiers.
Access Specifier | Allowed in same Class ? | Allowed in the same package? | Allowed in Subclass? (Different Package) | Allowed in other Packages? |
private | Yes | No | No | No |
default | Yes | Yes | No | No |
protected | Yes | Yes | Yes | No |
public | Yes | Yes | Yes | Yes |
class Example { private int privateVar = 10; // Only accessible within this class int defaultVar = 20; // Accessible within the same package protected int protectedVar = 30; // Accessible within the same package & subclasses public int publicVar = 40; // Accessible from anywhere }
Access specifiers on methods determine their visibility in the class hierarchy.
Access Specifier | Allowed in the same Class? | Allowed in the same Package? | Allowed in the Subclass? (Different Package) | Allowed in the other Packages? |
private | Yes | No | No | No |
default | Yes | Yes | No | No |
protected | Yes | Yes | Yes | No |
public | Yes | Yes | Yes | Yes |
Write a program to override a protected method
class Parent { protected void display() { System.out.println("Parent's Protected Method"); } } class Child extends Parent { @Override public void display() { // Allowed (Can increase visibility to public) System.out.println("Child's Public Method"); } }
Access specifiers can be used on constructors to control object creation.
class Example { private Example() { } // Cannot create an object from outside this class Example(int x) { } // Accessible within the same package protected Example(String str) { } // Accessible within package and subclasses public Example(double y) { } // Accessible from anywhere }
Write a program using a private constructor ( Singleton design pattern )
class Singleton { private static Singleton instance; private Singleton() { } // Private constructor public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
1. Methods in interfaces are always public (before Java 9).
2. Fields in interfaces are public static final by default.
interface MyInterface { int VALUE = 10; // public static final by default void display(); // public abstract by default }
Access specifiers play a crucial role in ensuring the encapsulation, security, and maintainability of your Java applications. Below are some best practices to follow when using them.
1. Always declare instance variables as private to prevent direct access and modification.
2. Use public getters and setters to control access to private fields.
public class Student { private int rollNumber; // Private field public int getRollNumber() { // Public getter return rollNumber; } public void setRollNumber(int rollNumber) { // Public setter if (rollNumber > 0) { this.rollNumber = rollNumber; } } }
1. Public fields can be modified from anywhere, breaking encapsulation.
2. Instead, use private fields with public methods.
Bad Practice (Avoid This):
public class Car { public int speed; // Anyone can change this directly }
Good Practice:
public class Car { private int speed; // Data hiding public int getSpeed() { return speed; } public void setSpeed(int speed) { if (speed >= 0) { // Adding validation this.speed = speed; } } }
1. Keep methods private unless they need to be accessed outside the class.
2. Mark methods protected or default if they should be used only within a package or subclass.
3. If a method is only used inside the class, make it private.
1. The protected members should only be used for methods or fields that must be accessed by subclasses.
2. Do not use protected if the field/method is not meant to be overridden.
1. If a class, method, or variable is only needed within the same package, do not make it public.
2. Use default access (no modifier) to restrict visibility within the package.
1. Avoid making every class public unless necessary.
2. If a class is only needed within a package, keep it default (package-private).
1. If a method should only be implemented by subclasses, mark it protected instead of public.
2. If a method should not be accessible outside, keep it private.
Access specifiers in Java play a crucial role in controlling access to classes, methods, and variables, ensuring encapsulation, security, and maintainability. By using them effectively, developers can protect data, restrict unintended access, and enforce proper design principles in their applications.
private – Best for encapsulation; restricts access within the class.
default (package-private) – Limits access within the same package; useful for internal implementation.
protected – Allows access in subclasses and within the same package; useful in inheritance.
public – Provides unrestricted access across the application; should be used judiciously.
Following best practices—such as keeping fields private, exposing only necessary methods, and restricting access when possible—leads to more secure, modular, and maintainable Java applications. Mastering access specifiers is a fundamental step toward writing clean and efficient Java code.
So this is all about access specifiers in Java. If you have any questions on this topic, please raise them in the comments section. If you liked this article, then please share this post with your friends and colleagues.