Access Modifiers in Java

Introduction

Modifiers are keywords that let us fine-tune access to our class and its members, their scope, and behavior in certain situations. For example, we can control which classes/objects can access certain members of our class, whether a class can be inherited or not, whether we can override a method later, whether we should override a method later, etc.

Modifier keywords are written before the variable/method/class (return) type and name, e.g. private int myVar or public String toString().

Modifiers in Java fall into one of two groups - access and non-access:

  • Access: public, private, protected
  • Non-access: static, final, abstract, synchronized, volatile, transient, and native

Want to learn more about non-access modifiers? Check out our article Non-Access Modifiers in Java.

Access Modifiers

Access modifiers deal with the visibility of class members. They control whether other classes can see or change certain variables/methods of our class.

These types of modifiers are closely related to an important part of Object Oriented Programming called encapsulation. As a reminder, encapsulation is an idea that links data with the code that manipulates it. By controlling access, you can prevent misuse.

For example, by making sure that certain variables can only be accessed through well-defined methods (the typical get/set combination of methods) we make sure that we won't encounter any unexpected values or deny outside access to certain variables/methods altogether.

As previously mentioned, there are three access modifiers: public, private, and protected. Java also provides default access control (when no modifier is specified), which behaves similarly to protected.

  • public - the member can be accessed from anywhere
  • protected - the member is only inaccessible from non-subclasses in a different package
  • default (package-private) - also known as package access, the member can be accessed by any class within the same package
  • private - the member can only be accessed by other members within the same class

This table shows all possible access scenarios for class members:

Private Default Protected Public
Same class Yes Yes Yes Yes
Subclass (same package) No Yes Yes Yes
Non-subclass (same package) No Yes Yes Yes
Subclass (different package) No No Yes Yes
Non-subclass (different package) No No No Yes

This table applies only to class members, not classes in general. A non-nested class can only be public or without a modifier. The behavior is logical, when a class is declared without a modifier it can only be accessed by code within the same package, and when it is declared public it can be used in a different package as well.

Note: A public class must be the only (non-nested) class in the file, and the file must have the same name as the class.

For example, let's say we have two packages, creatively named packageOne and packageTwo.

package packageOne;

public class MyPublicClass {
    String noModifierText = "No Modifier";
    private String privateText = "Private Text";
    protected String protectedText = "Protected Text";
    public String publicText = "Public Text";

    public MyPublicClass() {
        // We can access all members of a class from within that class
        System.out.println("MyPublicClass constructor:")
        System.out.println(noModifierText);
        System.out.println(privateText);
        System.out.println(protectedText);
        System.out.println(publicText);
    }
}

Note that the above code is in a file called "MyPublicClass.java". The name must match the class since we'll make the class public so we can access it from a different package. The same applies to the other classes below.

package packageOne;

class SamePackageExtends extends MyPublicClass {
    public SamePackageExtends() {
        System.out.println("SamePackageExtends constructor:")
        System.out.println(noModifierText);
        // Trying to access the private member privateText will fail, since private members
        // can only be accessed by members of the same class, even though this class extends it.
        // System.out.println(privateText);
        System.out.println(protectedText);
        System.out.println(publicText);
    }
}
package packageOne;

class SamePackageDoesntExtend {
    // Has the same access as SamePackageExtends
    public SamePackageDoesntExtend() {
        MyPublicClass myPublicClass = new MyPublicClass();

        System.out.println("SamePackageDoesntExtend constructor:")
        System.out.println(myPublicClass.noModifierText);
        // System.out.println(myPublicClass.privateText);
        System.out.println(myPublicClass.protectedText);
        System.out.println(myPublicClass.publicText);
    }
}
package packageTwo;

class DifferentPackageExtends extends packageOne.MyPublicClass {
    public DifferentPackageExtends() {
        System.out.println("DifferentPackageExtends constructor:")
        // System.out.println(noModifierText); // Same class or same package only
        // System.out.println(privateText);    // Same class only
        System.out.println(protectedText);
        System.out.println(publicText);
    }
}
package packageTwo;

class DifferentPackageDoesntExtend {
    public DifferentPackageDoesntExtend() {
        packageOne.MyPublicClass myPublicClass = new packageOne.MyPublicClass();

        System.out.println("DifferentPackageDoesntExtend constructor:")
        // System.out.println(myPublicClass.noModifierText);
        // System.out.println(myPublicClass.privateText);
        // System.out.println(myPublicClass.protectedText); // Same package only
        System.out.println(myPublicClass.publicText);
    }
}

Tip: It's common practice to encapsulate a class. This means that we declare member variables as private and declare public methods that manipulate them. For example, we want to let someone change int ID field but we also want to make sure that int ID is strictly a positive integer. Though the public method, we can first run a check and manipulate the field if the given value passes our check. This is a construct called a set() method, and it's usually accompanied by a get() method (since we can't read private members outside of our class) or when we want to control how and when the value of a variable can be read.

class GetSetExample {
    ...
    private int ID = 0; // Default value
    public setID(int n) {
        if (n > 0) {
            ID = n;
        }
        else ID = 0;
    }
    public int getID() {
        // Potential read conditions that need to be met

        return ID;
    }
    ...
}

One other thing to note is that protected is the least used of all of the access modifiers. It can easily be bypassed if we want to. Even in a different package we can simply inherit the class whose protected members we want to access, and then access them via that inherited class.

With that in mind, protected is most often used as a guideline that says "This member is not meant to be accessed by non-subclasses in a different package", so even though we can easily bypass protected access control, it's not advisable, since it was most likely put there for a reason.

Conclusion

Modifiers are keywords that let us fine-tune access to our class and its members, their scope and behavior in certain situations. They provide fundamental traits for our classes and their members. Every developer should be thoroughly be acquainted with them to make the best use of them.