Access specifiers in Java
Introduction
In Java, access specifiers (also called access modifiers) define the scope and visibility of classes, methods, variables, and constructors. They are an essential part of object-oriented programming because they enforce encapsulation, prevent misuse of code, and allow developers to design secure and maintainable applications.
By controlling how different parts of a program interact, access specifiers help you decide who can see what in your code.
✅What Are Access Specifiers?
Access specifiers in Java are keywords that control the visibility of classes and their members. Their primary purpose is to:
- Enforce encapsulation → Hide implementation details.
- Provide controlled access → Expose only what is necessary.
- Improve security → Prevent unauthorized use or modification.
- Enhance maintainability → Reduce unintended dependencies.
✅Types of Access Specifiers in Java
Java provides four levels of access control:
Default (Package-Private)
- No keyword is used.
- Accessible only within the same package.
Private
- Accessible only within the declared class.
- Cannot be applied to top-level classes.
Protected
- Accessible within the same package and by subclasses, even if they are in different packages.
- Useful in inheritance scenarios.
Public
- Typically used for APIs or classes designed for global use.
- Accessible from anywhere, across all packages and classes.
1. Default (Package-Private) Access
If no access modifier is specified, Java applies default (package-private) access.
class MyClass {
void display() {
System.out.println("Default access method");
}
}
- When no access modifier is specified, it is considered the default access modifier, which allows access to class members only within the same package.
- Useful for creating package-level APIs or services.
2. Private Access
Private members are strictly confined to their class.
public class Example {
private int age;
private void printAge() {
System.out.println("Age: " + age);
}
}
- The private access modifier restricts access to a class, method, or variable within the same class only; it cannot be accessed from any other class.
- Ideal for sensitive data (e.g., passwords, configurations).
- Note: Top-level classes cannot be private.
3. Protected Access
Protected members are more permissive than private but still controlled.
package animals;
public class Animal {
protected void sound() {
System.out.println("Animal makes a sound");
}
}
package pets;
import animals.Animal;
public class Dog extends Animal {
public void bark() {
sound(); // accessible because Dog is a subclass
}
}
- The protected access modifier allows access to class members within the same package and also to subclasses in different packages.
- Commonly used in inheritance for customization.
4. Public Access
Public members are universally accessible.
public class User {
public void login() {
System.out.println("User logged in");
}
}
- The public access modifier makes a class, method, or variable accessible from any other class, with no restrictions on its visibility.
- Often used for entry points (e.g.,
main
method), API methods, and utility classes.
Scope and Usage Table
Specifier | Class | Package | Subclass | World |
---|---|---|---|---|
Default | Yes | Yes | Yes (same package) | No |
Private | Yes | No | No | No |
Protected | Yes | Yes | Yes (even in other packages) | No |
Public | Yes | Yes | Yes | Yes |
✅ Access Modifiers with Classes
- public Class
- A
public
class can be accessed from any other class, in any package.
✔️Example:
// File: Vehicle.java
public class Vehicle {
public void display() {
System.out.println("This is a public class.");
}
}
// File: TestVehicle.java (in the same or different package)
public class TestVehicle {
public static void main(String[] args) {
Vehicle v = new Vehicle();
v.display(); // Accessible everywhere
}
}
- Default Class (Package-Private)
- If no modifier is specified, the class is package-private and accessible only within the same package.
✔️Example :
// File: Engine.java
class Engine {
void showType() {
System.out.println("This is a default class.");
}
}
// File: TestEngine.java (Same Package)
public class TestEngine {
public static void main(String[] args) {
Engine e = new Engine();
e.showType(); // Accessible because it's in the same package
}
}
👉 Classes cannot be declared private
or protected
.
✅Access Modifiers with Interface
By default, interface members (methods and variables) are public
.
- Methods are implicitly
public abstract
. - Variables are implicitly
public static final
.
public interface Animal {
// Implicitly public abstract method
void sound();
// Implicitly public static final variable
int LEGS = 4;
}
public class Dog implements Animal {
@Override
public void sound() {
System.out.println("Dog barks.");
}
public void showLegs() {
System.out.println("Legs: " + LEGS);
}
}
public class TestAnimal {
public static void main(String[] args) {
Dog dog = new Dog();
dog.sound(); // Output: Dog barks.
dog.showLegs(); // Output: Legs: 4
}
}
👉 Access modifiers cannot restrict interface methods or variables further.
✅ Access Modifiers in Inheritance
Inheritance allows a subclass to inherit fields and methods from a superclass. Access modifiers determine what can be inherited.
✔️Example of Access Modifier Effect in Inheritance
public class Parent {
public int publicVar = 1;
protected int protectedVar = 2;
int defaultVar = 3; // Package-private
private int privateVar = 4;
public void showAll() {
System.out.println("Public: " + publicVar);
System.out.println("Protected: " + protectedVar);
System.out.println("Default: " + defaultVar);
System.out.println("Private: " + privateVar);
}
}
public class Child extends Parent {
public void display() {
System.out.println("Access publicVar: " + publicVar); // Accessible
System.out.println("Access protectedVar: " + protectedVar); // Accessible
System.out.println("Access defaultVar: " + defaultVar); // Accessible if same package
// System.out.println("Access privateVar: " + privateVar); // ❌ Not accessible
}
}
public class TestInheritance {
public static void main(String[] args) {
Child c = new Child();
c.display();
}
}
✅ Summary Table – Access Modifiers in Inheritance
Modifier | Same Class | Subclass (Same Package) | Subclass (Different Package) | Other Classes |
---|---|---|---|---|
public | ✔ | ✔ | ✔ | ✔ |
protected | ✔ | ✔ | ✔ | ❌ |
default | ✔ | ✔ | ❌ | ❌ |
private | ✔ | ❌ | ❌ | ❌ |
✅Best Practices for Access Specifiers
- Use
private
by default → Start with the most restrictive access and open up only if required. - Encapsulate sensitive data → Keep variables private and expose them via getters/setters if necessary.
- Use
protected
carefully → Only for methods intended to be customized by subclasses. - Use
public
sparingly → Overexposing methods leads to tight coupling and harder maintenance. - Prefer default access → For internal classes and methods that should stay package-bound.
Common Mistakes to Avoid
- ❌ Declaring top-level classes as private/protected → Not allowed, only public or default is permitted.
- ❌ Overusing public → Leads to poor encapsulation and fragile code.
- ❌ Forgetting constructor visibility → A class with a private constructor cannot be instantiated from outside (common in Singleton design pattern).
- ❌ Mixing access with unrelated logic → Choose access levels based on design needs, not convenience.
Conclusion
Access specifiers are a fundamental concept in Java that every developer must master. By carefully choosing the right modifier—private
, default
, protected
, or public
—you can design classes that are secure, extensible, and maintainable.