The basics of Java access modifiers

Alonso Del Arte
12 min readDec 22, 2020
Photo by Jason Pofahl on Unsplash

Access is an important element of encapsulation in object-oriented programming. For example: can an instance of a given class access the fields of an instance of another class? That depends on what access level that particular field has been designated with.

Java has four access levels for class fields and “methods”: public, protected, package private and class private (usually called just “private”). I’ve listed these in order from least restrictive to most restrictive.

The module system introduced in Java 9 allows for even more fine-grained control, but I really won’t be covering that in this article. I assume that you’re either using Java 8 or you’re using Java 9 or later with all your classes in the same module, such as the default unnamed module.

So, if you’re using Java 9 or later with multiple modules, please imagine all further mention of public access to have the appropriate caveat about things that are not exportable to other modules.

As I understand them, modules are sort of super-packages. Everything that is declared public within a module can be accessed anywhere within that module. The visibility of a module’s public items to other modules is a topic I leave to someone else to explain.

Public items can be accessed outside the packages they’re defined in. Package private items can only be accessed within that package. Protected items can be accessed anywhere within the same package, and also outside of the package, but then only by subclasses.

Public corresponds to the reserved word public, protected is marked with protected and class private is marked with private (since package private is the default, it has no access modifier).

It almost goes without saying that there can only be one access modifier per declaration. It is, for example, impossible for something to be both public and private. A repeated modifier also causes a compilation error.

But, as long as the modifiers’ meanings are not contradictory or inapplicable, modifiers can be freely combined. There is, for example, no particular reason why a private function can’t also be in strict floating point mode (with the modifier strictfp).

Nor do the modifiers have to be in any particular order, though it’s customary to place an explicit access modifier before any other modifier that might apply, e.g., “public static final” rather than “final public static.”

Do note, however, that types are not modifiers. Something like “static int final public” would not compile. Modifiers first, type and then identifier, that’s the order they go in.

In this article, I’m only going to use toy examples. Here I’m more interested in giving an example of every possible situation than in giving a realistic example of how you might use access modifiers to enforce encapsulation in a real world project.

Here’s our first toy example:

package com.example;import java.io.Serializable;public class ToyExample implements Serializable {

private static final long serialVersionUID
= 4550389864381561651L;

public static final int INTEGER_CONSTANT = 1729;

protected int someInteger = -58;

double someNumber = Math.random() * someInteger;

private static int instantiationCount = 0;

public static int getInstantiationCount() {
return instantiationCount;
}

public ToyExample() {
instantiationCount++;
this.someNumber *= instantiationCount;
}

}

The ToyExample class per se can only be public or package private. By default, NetBeans makes new classes public and IntelliJ generally makes new classes package private. You can change those on a case-by-case basis, and you can probably also change the default.

It is quite typical for a constant like INTEGER_CONSTANT to be public and a variable like instantiationCount to be private. Then it may or may not be necessary to provide a public getter for the private variable.

Protected variables like someInteger and package private variables like someNumber have their uses, though perhaps more so in the earlier days of Java than nowadays.

We are generally more inclined nowadays to make variables private and provide getters and setters for the variables that need them.

But back in the days when Java was thought to be very sluggish compared to even unoptimized C++, it’s understandable that programmers would have worried that accessing a field through a getter or setter might incur an unacceptable performance penalty.

Thus they would have been reluctant to make every field class private. If there is still a performance penalty for accessing fields through getters and setters, it’s usually so small as to not be worth worrying about.

To illustrate the access of some of these ToyExample fields, here’s an example in a different package, the poorly named various package:

package various;import com.example.ToyExample;public class AccessDemo {

public static void main(String[] args) {
System.out.println(ToyExample.INTEGER_CONSTANT);
System.out.println(ToyExample.getInstantiationCount());
ToyExample easyExample = new ToyExample();
System.out.println(ToyExample.getInstantiationCount());
System.out.println(easyExample.someInteger); // ERROR!
System.out.println(easyExample.someNumber); // ERROR!
System.out.println(easyExample.instantiationCount);
// ERROR!
}

}

Of course AccessDemo’s main() can access ToyExample’s static constant INTEGER_CONSTANT, since it’s declared public. It can be accessed from any package, subject to the module caveat. The same goes for the static getter getInstantiationCount().

On the other hand, the protected, package private and class private fields of ToyExample are definitely inaccessible to AccessDemo’s main(). The lines trying to access someInteger, someNumber and instantiationCount all cause compilation errors.

Note that easyExample is a local variable, with its scope is limited to main(), and so it doesn’t need an access modifier. Nor will the compiler allow an access modifier for it (though your IDE might suggest you convert it to a field).

Although serialVersionUID is marked private, it can actually be accessed outside of the package. But that’s because of so-called “native methods.” If there are no such platform-dependent subroutines trying to access and of ToyExample’s private fields, the private access modifiers will be respected.

The same access modifiers can be applied to “methods.” And if you can call it, you can override it, provided of course it’s not marked final. I’ve added a few functions and procedures to ToyExample to illustrate.

package com.example;import java.io.Serializable;public class ToyExample implements Serializable {

private static final long serialVersionUID
= 4550389864381561651L;
// ... other fields omitted from quotation ... @Override
public String toString() {
String str = this.instanceLabel;
if (this.instanceFlag) {
str = str + this.instanceWeight;
}
return str;
}

public static final int getInstantiationCount() {
return instantiationCount;
}

protected void adjustInteger(int integer) {
this.someInteger = integer;
}

void adjustNumber(double number) {
this.someNumber = number;
}

private void makeNumbersEquals() {
this.someNumber = this.someInteger;
}
// ... constructor omitted from quotation ...}

Of course toString() is inherited from Object, and it’s expressly meant to be callable from any class and overridable by any class you might want to write.

Trying to narrow the access level for toString() will cause a compiler error. You may mark a toString() override final, though I can’t imagine why you’d ever want to do that, other than to see that you can.

However, it does make sense to mark getInstantiationCount() final, so I did. It can be called from any class, but it wouldn’t make sense for ToyExample subclasses to override it. Given that instantiationCount is private, a subclass can’t meaningfully override getInstantiationCount().

If you’ve been following along in your IDE, I would like you to create two ToyExample subclasses, one in the same package, and another one in a different package.

I’m gonna name mine SamePkgSub and DiffPkgSub, and place the latter in the same poorly named various package as AccessDemo. Also, I’m going to add a couple of overrides to SamePkgSub:

package com.example;public class SamePkgSub extends ToyExample {

@Override
protected void adjustInteger(int integer) {
this.someInteger &= 65535;
}

@Override
void adjustNumber(double number) {
this.someNumber = Math.exp(number);
}

}

This class can’t override getInstantionCount() even though it’s public, because it’s also final. But both adjustInteger() and adjustNumber() are fair game, even though the former is protected and the latter is package private.

I’m going to leave makeNumbersEqual() alone, it can’t be overridden even though it’s not explicitly marked final: it’s effectively final. You can certainly add “final” in there if you want, but your IDE should give you a warning for the essentially redundant modifier.

Now try copying both overrides to the different package subclass. The adjustInteger() override should be fine, but adjustNumber() should cause an error (or two errors, if you include the override annotation).

Neither of those can be called from AccessDemo. It’s up to you whether you want to keep or delete what you previously had in AccessDemo’s main(), if you’re following along.

package various;import com.example.SamePkgSub;
import com.example.ToyExample;
public class AccessDemo {

public static void main(String[] args) {
System.out.println(ToyExample.getInstantiationCount());
ToyExample easyExample = new ToyExample();
System.out.println(ToyExample.getInstantiationCount());
ToyExample samePkgSub = new SamePkgSub();
System.out.println(ToyExample.getInstantiationCount());
ToyExample diffPkgSub = new DiffPkgSub();
System.out.println(ToyExample.getInstantiationCount());
samePkgSub.adjustInteger(12); // ERROR!
diffPkgSub.adjustInteger(13); // ALSO AN ERROR

}

}

The constructor of a class doesn’t have to have the same access level as the class itself. The constructor can have any of the four access levels. Though in my opinion it doesn’t make much sense for a package private class to have a public constructor. The compiler will allow it, though.

By contrast, it can be very useful for a public class to have only a private constructor. This may be done to restrict instantiation. The class might provide a static function, typically called getInstance(), as the only means for obtaining instances of the class.

This is similar to what happens with enumerated types. You may write a constructor for an enumerated type, but it must be private, though you may omit “private” from the declaration.

package time;public enum DayOfTheWeek {

SUNDAY ("Su", '\u2609'),
MONDAY ("M", '\u263E'),
TUESDAY ("Tu", '\u2642'),
WEDNESDAY ("W", '\u263F'),
THUSDAY ("Th", '\u2643'),
FRIDAY ("F", '\u2640'),
SATURDAY ("Sa", '\u2644');

private final String abbreviation;

private final char alchemSymbol;

public String getAbbreviation() {
return this.abbreviation;
}

public char getSymbol() {
return this.alchemSymbol;
}
DayOfTheWeek(String abbrev, char symbol) {
this.abbreviation = abbrev;
this.alchemSymbol = symbol;
}

}

The DayOfTheWeek enumeration is not a realistic example because for most purposes it makes more sense to use java.time.DayOfWeek than to reinvent this particular wheel.

Even though the DayOfTheWeek constructor appears to be package private, it’s actually class private, and your IDE will show it with the same padlocked icon as private constructors in regular classes (at least NetBeans does in its Navigator pane).

Screenshot of the Navigator pane in NetBeans 11.2.
Left: Detail of the Navigator pane for a regular class with four constructors in NetBeans. Right: Also in NetBeans, detail of the Navigator pane for an enumerated type, showing the constructor is regarded by NetBeans as private, even though it’s not explicitly marked as such.

The java.time.DayOfWeek enumeration doesn’t have an explicit constructor. The compiler will nevertheless always make a sole private constructor for all enumerated types, based on the sole protected constructor for Enum<E>.

And if you do write an explicit constructor for an enumerated type, the compiler will put in bytecode equivalent to the appropriate super() call (you can try to write that yourself, but it will be flagged as an error).

Of course a Java class that is not an enumerated type may have multiple constructors. And it’s even possible to have at least one constructor for each of the access levels.

For the next example, let’s add these three fields to ToyExample:

    private final String instanceLabel;

private final double instanceWeight;

private final boolean instanceFlag;

Then let’s write these four constructors:

    public ToyExample() {
this("", 0.0, false);
}

protected ToyExample(String label) {
this(label, 1.0, true);
}

ToyExample(String label, double weight) {
this(label, weight, true);
}

private ToyExample(String label, double weight, boolean flag) {
this.instanceLabel = label;
this.instanceWeight = weight;
this.instanceFlag = flag;
instantiationCount++;
this.someNumber *= instantiationCount;
}

I emphasize again that this is a toy example. It is likelier, in a real world project, to have multiple constructors for only one or two access levels, like maybe just public and private.

Even the String class is an example of this. Although it has something like two dozen constructors, almost all of them are public and one of them is package private (I’m looking at the Oracle JDK for Java 8).

Maybe in another JDK version there might be a private constructor or two for String, but I doubt you’ll find one with a protected constructor. That’s because, you see, String is marked final, it can’t be subclassed.

You might think that a class marked final is not allowed to have a protected constructor. I thought the same thing. But, as it turns out, the compiler allows it. In such a case, the protected constructor ends up being quite similar to a package private constructor.

So, regardless of whether or not a class is marked final, you can write constructors at each access level that you want, but in practice you should only write constructors at each access level your class needs.

Since AccessDemo is not a subclass of ToyExample, and they’re in different packages, it would seem that the rather limited public ToyExample constructor is the only ToyExample constructor which AccessDemo can use.

Actually, AccessDemo can also use the protected constructor without AccessDemo subclassing ToyExample, by using an anonymous subclass inline.

package various;import com.example.SamePkgSub;
import com.example.ToyExample;
public class AccessDemo {

public static void main(String[] args) {
System.out.println(ToyExample.getInstantiationCount());
ToyExample easyExample = new ToyExample();
System.out.println(ToyExample.getInstantiationCount());
ToyExample samePkgSub = new SamePkgSub();
System.out.println(ToyExample.getInstantiationCount());
ToyExample diffPkgSub = new DiffPkgSub();
System.out.println(ToyExample.getInstantiationCount());
ToyExample anonymousExample
= new ToyExample("Anonymous Example") {

// WARNING: No equals() override

@Override
public int hashCode() {
int addend = ToyExample.INTEGER_CONSTANT
* this.someInteger;
}

};

System.out.println(easyExample.someInteger); // ERROR!!!
System.out.println(anonymousExample.someInteger);
// ALSO AN ERROR
}

}

As you can see, anonymousExample can use the protected ToyExample constructor and its hashCode() override can access the protected ToyExample field someInteger.

But outside of the anonymousExample declaration, AccessDemo can’t access the someInteger field of any ToyExample instance.

Of course the anonymous subclass defined for anonymousExample is purely local. The anonymousExample instance can be passed outside of AccessDemo, but it would more likely be understood as a ToyExample instance (assuming this class had any practical usefulness).

For what it’s worth, I don’t like anonymous classes too much. I think they’re too often used for things that can be reused (such as file choosers that ask the user to confirm before overwriting existing files), leading to unnecessary duplication in the project.

Even nested classes are more amenable to reusability than anonymous classes. Even if the nested class is marked class private. They’re also more amenable to testing, if they’re not marked class private.

The terminology of nested classes is a little confusing, but when it comes to choose an access modifiers for a nested class, it doesn’t actually matter whether the nested class is “static” or not. All four access levels are possible for a nested class.

However, do note that a nested non-static class can access the private fields of the containing class, as the following example demonstrates:

public class ToyExample {

private int counter;

public void advanceCounter(int increment) {
this.counter += increment;
}

public ToyExample(int initCounter) {
this.counter = initCounter;
}

class NestedNonStatic {

public void revealCounter() {
System.out.println(counter);
}

}

static class NestedStatic {

public void displayCounter() {
System.out.println(counter); // ERROR!!!
}

}


}

(I cleared out some things from prior examples.)

The counter referenced by revealCounter() in ToyExample.NestedNonStatic is the counter marked as a private ToyExample field. But access to that private field is denied to ToyExample.NestedStatic. In some ways, a nested static class is no different from an outer class.

By the way, you can nest an interface inside of a class or inside another interface. But unlike a nested class, an interface nested inside another interface can’t be protected or private.

However, an interface nested in a class may be protected or class private. I guess this makes sense. I didn’t even know this could be done at all, so I won’t give an example of this, practical or not.

With multiple nesting levels, the combinations of access modifiers can get very complex. But that complexity’s not worthwhile, in my opinion.

Like outer classes, outer interfaces can either be public or package private. Up to Java 7, there was only one logical choice of access modifier for interface functions and procedures: the exact same as the interface itself.

Consider this toy example:

package com.example;public interface ItemSupplier {

public abstract Item supply(Parameter... parameters);
}

Since Java 7 interfaces can’t provide implementations, it’s unnecessary to mark supply() as abstract. It’ll still compile, but IntelliJ gives a warning.

Java 8 does allow interfaces to contain implementations, but, perhaps mostly so as to not break older programs, does not require the “mandatory methods” to be marked abstract.

So IntelliJ also considers “abstract” to be redundant for Java 8 interfaces, and will offer to remove all redundant modifiers.

This leads to somewhat of an inconsistency between classes and interfaces: the absence of an access modifier in a class always means package private, but in an interface it always means public… except on the declaration line (the line with the reserved word “interface”).

I hope this has either been a good introduction or a welcome refresher. But I haven’t said anything about when you should choose a particular modifier and why. I’m going to write a separate article about that.

The use of toy examples can exacerbate a certain feeling of pointlessness, a feeling that you have many fine-grained options for access levels, but no practical reason to take full advantage of them. That’s what I will address in the next article.

--

--

Alonso Del Arte

is a Java and Scala developer from Detroit, Michigan. AWS Cloud Practitioner Foundational certified