Java Access Modifiers: public, private, protected, Controlling Visibility

In Java, the code we write is composed of classes and their members (methods, variables, etc.). To make code safer and more maintainable, Java provides access modifiers to control the visibility of these members—meaning where they can and cannot be accessed. In this article, we’ll discuss the three most commonly used access modifiers: public, private, and protected, and how they control visibility.

Why Access Modifiers?

Imagine if all class members were directly accessible from anywhere. The code could be arbitrarily modified or misused. For example, if a bank account balance could be directly modified by anyone, the consequences would be disastrous. Access modifiers act like “gatekeepers,” controlling who can enter and who cannot.

1. public: Public, Accessible Everywhere

public is the most “open” modifier, meaning members are visible to all classes, whether in the same package or different packages.

Example:
Suppose we have a Person class with a public method accessible by any class anywhere.

// Person class in package com.example
package com.example;

public class Person {
    // Public method accessible by all classes
    public void sayHello() {
        System.out.println("Hello!");
    }
}

// Another class in package com.test (different package)
package com.test;

import com.example.Person; // Import Person class

public class OtherClass {
    public static void main(String[] args) {
        Person person = new Person();
        person.sayHello(); // Successfully called! Public method accessible across packages
    }
}

Conclusion: public members can be directly accessed by any class, including those in different packages.

2. private: Private, Only Accessible Within the Class

private is the most “restrictive” modifier, meaning members can only be accessed within the class where they are defined. Other classes (including those in the same package) cannot access them directly.

Example:
Suppose Person has a private variable; other classes (even in the same package) cannot directly read or modify it.

// Person class in package com.example
package com.example;

class Person {
    private String name; // Private variable: only accessible within Person

    private void printName() { // Private method: only accessible within Person
        System.out.println("Name: " + name);
    }

    // Public methods to indirectly access private members
    public void setName(String n) {
        name = n;
    }

    public void getName() {
        printName(); // Internal call to private method
    }
}

// Same package class OtherClass trying to access Person's private members
package com.example;

public class OtherClass {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("Alice"); // Valid: via public method
        person.getName(); // Valid: via public method

        // Error! Private members cannot be accessed directly
        // System.out.println(person.name); // Compilation error: Cannot access private member
        // person.printName(); // Compilation error: Cannot access private method
    }
}

Conclusion: private members are only accessible within the class. External classes must use public methods (e.g., getter/setter) to access them indirectly.

3. protected: Protected, Accessible Within the Package and Subclasses

The protected modifier’s visibility lies between public and private, with two key scenarios:
- Same Package: Classes in the same package can access protected members directly (similar to public).
- Subclasses in Different Packages: If a class inherits from the class defining protected members, the subclass can access these members (even if in a different package).

Example 1: Access Within the Same Package

// Classes A and B in the same package com.example
package com.example;

class A {
    protected int age = 20; // Protected variable accessible within the package
}

class B {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(a.age); // Valid: Direct access within the package
    }
}

Example 2: Subclass in a Different Package Access

// Parent class Person in package com.example
package com.example;

public class Person {
    protected String name; // Protected variable

    protected void showName() {
        System.out.println("Name: " + name);
    }
}

// Subclass Student in package com.test (different package) inherits from Person
package com.test;

import com.example.Person; // Import parent class

public class Student extends Person {
    public void displayInfo() {
        name = "Bob"; // Subclass accesses parent's protected variable
        showName();   // Subclass accesses parent's protected method
    }
}

// Test class in package com.test (tries to access non-subclass protected member)
package com.test;

import com.example.Person;

public class Test {
    public static void main(String[] args) {
        Person person = new Person();
        // Error! Test is not a subclass of Person; protected members are not accessible across packages by non-subclasses
        // System.out.println(person.name); // Compilation error
    }
}

Conclusion: protected members are accessible within the same package and by subclasses in different packages, but not by non-subclass classes in different packages.

4. Default Modifier (No Modifier): Accessible Within the Package Only

If a member has no modifier (the “default” modifier), it is visible only within the same package. Classes in different packages cannot access it.

Example:

// Classes A and B in the same package com.example
package com.example;

class A {
    int count = 10; // Default modifier: accessible within the package
}

class B {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(a.count); // Valid: Direct access within the package
    }
}

// Class in different package com.test (tries to access default member)
package com.test;

import com.example.A;

public class Test {
    public static void main(String[] args) {
        A a = new A();
        // Error! Default members are not accessible across packages
        // System.out.println(a.count); // Compilation error
    }
}

Conclusion: Default-modifier members are only visible within the same package.

Summary: Access Modifier Comparison Table

Modifier Same-Package Classes Different-Package Non-Subclasses Different-Package Subclasses Other Packages
public ✅ Yes ✅ Yes ✅ Yes ✅ Yes
private ❌ No ❌ No ❌ No ❌ No
protected ✅ Yes ❌ No ✅ Yes ❌ No
Default ✅ Yes ❌ No ❌ No ❌ No

Practical Development Recommendations

  • Prefer private for Member Variables: Use public methods (e.g., getter/setter) to control access. This ensures data security and allows logic like data validation within the methods.
  • Control Class Visibility: Use the default modifier if a class is only used within its package; use public if it needs to be accessible by other packages.
  • Use protected for Inheritance: Use protected when a parent class needs to expose members to subclasses but not to other classes.

Mastering access modifiers is the first step toward writing secure and maintainable code!

Xiaoye