Java access modifiers: which to choose when, and why
On one of those question-and-answer websites, someone asked: What is the point of Java access modifiers if you can always just change them? It’s a question that crossed my mind when I was starting to learn Java years ago.
The student learns what the access modifiers are, and their semantics. And hopefully the student is given some guidance as to how to choose a modifier in the most common situations.
For this article, I assume you already know what the access modifiers are. If not, I wrote another article which only covers the basics, without any comment on the rationale for choosing one modifier and not another.
To a Java beginner, the access levels suggest levels of information security. But then this is not a very secure system, because anyone who can edit the source can change the access modifiers.
It’s also possible, but more complicated, to bypass access modifiers using platform-dependent subroutines, or “native methods.” The access modifiers can also be changed in the bytecode for the Java Virtual Machine (JVM).
That’s the wrong way to look at it. Access modifiers don’t indicate levels of information security, but rather they declare your intentions in regards to encapsulation. Access modifiers enable the Java compiler and other tools to enforce your decisions on the encapsulation.
Computers are better at keeping track of state than humans. So you decide that a certain variable can only be accessed from such and context. But maybe you forget and you try to access the variable from a different context.
If you marked it correctly, you’re going to get an error message and immediately remember your earlier decision regarding access. Also, an integrated development environment (IDE) like NetBeans will not suggest for auto-complete things that are not accessible from the context at hand.
As humans, we’re free to change our minds. While writing a Java program, we can revisit our choices of access modifiers. But once we publish a Java program’s source or Java archive (JAR), we’re making certain commitments to the people who use our program.
Therefore, it’s best to start off with the most restrictive access modifiers we can use, and loosen them only if we make a conscious decision that our initial choices were too restrictive.
In order from most restrictive to least restrictive, the Java access modifiers are: private
(class private), tacit in a class (package private), protected
(which also allows package access) and public
(usually tacit in an interface).
This all sounds very theoretical. We need an example with at least a little bit of realism to help make it concrete. I’m going to use an abstract class to represent savings or checking accounts.
However, the class I’m going to present here is a very rough draft. I do this, rather than present a finished and polished class, to give some idea of the decision-making process in choosing the access modifiers.
If you want to follow along in your own IDE, put the Account
class is in the bankaccounts
package.
The CurrencyAmount
class should come from a different package, a package that only deals with money amounts (remember that single responsibility applies to packages, too), if you choose not to use a third party library like Joda Money to represent money amounts. That’s good. Just don’t use primitives like double
.
We’re also going to need a Customer
class and a Person
class. Those can be very rudimentary at this point, but they also need to be in another separate package apart from bankaccounts
.
From the Java Development Kit (JDK), I think we’re going to need java.io.Serializable
, and ArrayList<E>
and Currency
from java.util
.
Here’s the class declaration:
public abstract class Account implements Serializable {
NetBeans filled in “public
” for me. For the class access modifier, there are only two choices: public or package private.
At this early stage, we can make Account
package private. And that might be good enough for the sake of this article. But in a real world use case, it’s almost a certainty that we will need to use Account
outside of the bankaccounts
package.
Next, a number for serialization purposes:
private static final long serialVersionUID
= 4548971533036436275L;
The access modifier for serialVersionUID
is a rather strange case that I will revisit later. For now, let’s move on to the class fields.
private final Customer accountHolderPrimary;
private Customer accountHolderSecondary = null;
private Person beneficiary = null;
private final ArrayList<Transaction> history
= new ArrayList<>();
private final Currency currency;
private CurrencyAmount balance;
These fields are going to have getters, and, when applicable, setters. These fields are private because we want the class to control how these fields are accessed. We don’t want other classes to change these fields in ways that could cause us troubleshooting headaches.
Maybe we can use Lombok to write the getters for us, but I think the setters for this particular class have some special validation requirements that Lombok can’t help us with.
So for now I will put in some TODO comments to remind ourselves we need to revisit these getters and setters at code review. Though at this point it seems clear to me that the getters and setters should be public.
Here are the first draft getters and setters for primary account holder, secondary account holder, and beneficiary.
// TODO: Flag for code review
public Customer getPrimary() {
return this.accountHolderPrimary;
}
// TODO: Flag for code review
public Customer getSecondary() {
return this.accountHolderSecondary;
}
// TODO: Add parameter validation
public void setSecondary(Customer customer) {
this.accountHolderSecondary = customer;
}
// TODO: Flag for code review
public Person getBeneficiary() {
return this.beneficiary;
}
// TODO: Add parameter validation
public void setBeneficiary(Person person) {
this.beneficiary = person;
}
Presumably this account only deals with one currency, like maybe United States dollars (USD).
public Currency getCurrency() {
return this.currency;
}
Here we’re not worried that the Currency
instance will get mutated by the getter’s caller, but we don’t want the account’s currency being changed accidentally, so the field is marked private.
We know very well that ArrayList<E>
is mutable, so theoretically the caller of the transaction history getter could unwittingly make changes to the account’s transaction history. For that reason, we use the ArrayList<E>
copy constructor in the transaction history getter.
public ArrayList<Transaction> getHistory() {
return new ArrayList<>(this.history);
}
This illustrates the difference between encapsulation and information security. We’re not worried that an external hacker will add or delete transactions from an account. Presumably the bank is paying cybersecurity experts to keep the hackers at bay.
But we are worried that someone on our team might make a mistake and inadvertently make incorrect changes to the transaction history.
Suppose for example that someone working on the bank website is working on the page that shows a customer a selection of their recent transactions according to a specific criterion, or combination of criteria, like transaction type, date range, minimum or maximum amount, etc.
Our colleague writes a call to the transaction history getter, and that caller proceeds to delete from the list all transactions that don’t fit the specified search criteria.
For example, say the account holder pulls up a list of recent point of sale withdrawals. Well, guess what: they’ve just deleted their ATM withdrawals, branch visit withdrawals, direct deposits, etc.
Maybe it’s okay, maybe they’re only viewing transactions and the database isn’t altered. But what if the customer can execute some transactions on the bank website, and then that causes the incomplete list of transactions to get written back to the database, overwriting the more complete list?
Except that it doesn’t actually matter because the caller received a copy of the list rather than the list itself. The caller is free to do to the copy of the list whatever is necessary, leaving the original list alone.
And if the website can add transactions, it should do so through the appropriate procedure defined by the Account
class. The website should not be able to write transactions directly to an Account
’s transaction history.
My first draft of the balance getter is convoluted, but it doesn’t give away access to any private fields.
// TODO: Refactor to be more efficient
public CurrencyAmount getBalance() {
CurrencyAmount bal = new CurrencyAmount(0, this.currency);
for (Transaction trx : this.history) {
bal = bal.plus(trx.getAmount());
}
return bal;
}
To be clear: Deposit
and Withdrawal
are subclasses of the abstract Transaction
class. They may go in the bankaccounts
package, but perhaps it would be better to put them in the bankaccounts.transactions
“sub-package.”
I’m thinking that a Deposit
’s amount should always be positive and a Withdrawal
’s amount should always be negative. That way, updating the account balance is a simple matter of adding up the transaction amounts.
The getBalance()
function is another good example of the difference between encapsulation and information security.
The account balance inquiry is public to all classes in our system that need to reference an Account
object. But it’s not public to random people on the street, or even most bank customers.
Maybe a bank’s teller can look on anyone’s account, but they should only disclose account information to the account holders and other authorized parties. That’s going to be enforced by the tellers’ manager, not by the access modifiers we choose in the program source.
I’m going to put in TODO comments in the procedures to process deposits and withdrawals, and also some “pseudocode.”
protected void processDeposit(Deposit deposit) {
// TODO: Implement
// Check currency (convert if needed, charge fee)
// Add deposit to history, update balance up
}
protected void processWithdrawal(Withdrawal withdrawal) {
// TODO: Implement
// if this.balance < withdrawal amount
// throw new insufficient balance exception
// Add withdrawal to history, update balance down
}
The Deposit
and Withdrawal
classes should both include a transaction date field; this will be important later in this article.
Use Joda Time, or something from java.time
, or a custom solution. Even the awful java.util.Date
would be better than using a primitive or a String
. And maybe the date of initial deposit should be a private Account
field.
I marked both of these transaction processing procedures protected because of inheritance. We might want to override processWithdrawal()
in CheckingAccount
to allow overdraft transfers from an associated savings account, for example.
However… protected implies that Account
needs to be extended by classes outside of the bankaccounts
package. As I think more about it, I think maybe that shouldn’t be allowed, maybe those two procedures should be package private. If you agree, delete “protected
” from both of them, thus making them package private.
There are of course cases in which you want to make a function or procedure callable and overridable from outside of the package. For example, notice that I’ve unthinkingly chosen ArrayList<Transaction>
to hold the account’s list of transactions, from account opening to the present.
It may or may not be the best choice in the Java Development Kit (JDK). We might want to use a third-party data structure library. Or we may want to make our own data structure from scratch.
More likely, though, we might find that the data structures in the JDK would be perfect for our needs if only we could tweak them. That’s why the JDK includes interfaces and abstract classes that we can use to make our own data structures without having to start from square one.
For example, the direct superclass of ArrayList<E>
is AbstractList<E>
. Both of those are in java.util
. Nothing forbids an ordinary Java programmer from placing their own classes in java.util
, except good taste.
So if a team needs to extend AbstractList<E>
, but they’re not working on a version of the JDK (such as OpenJDK), they will almost certainly place that subclass in a different package.
The removeRange()
procedure in AbstractList<E>
is protected. This means, for example, that in Account
we can’t write things like
this.history.removeRange(4, 7); // ERROR: Won't compile
because Account
is not in the java.util
package and it’s not a subclass of AbstractList<E>
. But if we were writing a subclass of AbstractList<E>
, we could write a call to removeRange()
regardless of what package our subclass is in, and even override it if we needed to.
Now I’m thinking that all account classes should be in the bankaccounts
package, and therefore processDeposit()
and processWithdrawal()
can be package private.
Of course processTransaction()
should be public. The caller should be able to call processTransaction()
with any Transaction
object whatsoever, leaving it to that procedure to route the processing to the appropriate package private sub-procedure.
Because Java’s Switch-Case isn’t as powerful as Scala’s Match-Case, we’re going to need some clunky casting in processTransaction()
. But because we’re essentially sketching at this stage, we needn’t quite worry about the specifics of that just yet.
public void processTransaction(Transaction transaction) {
// Check transaction date is after initial deposit date
// transaction match {
// case dep: Deposit => processDeposit(dep)
// case draw: Withdrawal => processWithdrawal(draw)
// case _ =>
this.history.add(transaction);
// }
}
If you’re following along with your IDE, you’re probably getting a warning that balance
can be final. This will be addressed pretty soon. But of course that warning is overshadowed by the errors for fields that are not initialized in the default constructor. So let’s put in a constructor.
public Account(Customer customer, Deposit initialDeposit) {
this.accountHolderPrimary = customer;
this.processDeposit(initialDeposit); // WARNING!!!
this.balance = initialDeposit.getAmount();
this.currency = this.balance.getCurrency();
}
It certainly makes sense that the constructor of a public class should also be public. But a constructor can be set to any access level, including private. In fact, the Currency
constructor is private precisely to ensure that there is only one Currency
instance for each currency recognized at runtime.
Enumerated types (defined with “enum
”) also use this private constructor concept to limit instantiation to the static initialization phase.
You don’t write a constructor per se for an enumerated type. Instead, the compiler supplies it, along with a static initializer that is called once for each instance of the enumerated type (e.g., seven times for DayOfWeek
).
Also, a Java class can have multiple constructors. So, theoretically, one Java class could have one constructor for each access level. In this example, however, I think Account
only needs one constructor, a public constructor.
This constructor should clear all the errors. But if you’re using NetBeans, you should be getting a warning about an “overridable method call.” As far as I can tell, neither Eclipse nor IntelliJ give this warning.
Maybe I’ll write an article explaining why such a call is bad. Here I’ll just say that this is a warning you want to address, not ignore, much less suppress.
The usual way to clear an “overridable method call” warning is to make the class final. But that’s not an option with Account
, since, as you know, an abstract class can’t also be final, at least not in Java.
Another option is to make the “overridable method” private. If subclasses can’t call it, they definitely can’t override it either. But that doesn’t seem quite appropriate in this case, either.
This is what I came up with to address the warning:
public Account(Customer customer, Deposit initialDeposit) {
this.accountHolderPrimary = customer;
CurrencyAmount depositAmount = initialDeposit.getAmount();
assert depositAmount.getAmountInCents() > 0
: "Initial deposit amount should be positive";
this.history.add(initialDeposit);
this.balance = depositAmount;
this.currency = this.balance.getCurrency();
}
Problem with this is that it looks like it’s duplicating what processDeposit()
does. And it is, with one crucial difference: the initial deposit is unique in that it is the first deposit to an account.
Meaning that all other transactions must be dated after it. It makes no sense to post a withdrawal and date it prior to the initial deposit, for example. So the constructor doesn’t need to check the date but processTransaction()
does, with isBefore()
, isAfter
or whatever mechanism the class to represent dates provides.
Some banks will let you run your balance in the red, and even compound the problem with outrageous fees. But to my knowledge, no bank will let you open an account with zero money.
Another detail that makes the initial deposit unique is that there is almost always a minimum requirement for an initial deposit, and it’s much greater than one cent, like, say, a hundred dollars.
That minimum may or may not be the same as the threshold to not be charged a minimum balance fee at the end of the month.
The threshold might be declared as a class constant, in which case it should probably be public, since it will presumably be needed outside of the bankaccounts
package. For example:
public static final CurrencyAmount MINIMUM_BALANCE
= CurrencyAmount.parseAmount("$100.00");
Given that the Account
constructor requires an initialDeposit
object, it stands to reason that the balance field can start with whatever amount the initialDeposit
object carries, rather than zero.
At some point we will probably write a test that requires the Deposit
constructor to throw an IllegalArgumentException
if the deposit amount is negative. Or maybe we’ve already done that.
But if we haven’t yet, an assertion in the Account
constructor could be a helpful reminder. Remember that assertions have to be turned on for an Assert statement to have any effect, but all unit testing frameworks turn them on (well, at least JUnit does).
Later on, during refactoring or code review, we could decide that we no longer need an assertion that the deposit amount is positive in Account
since the Deposit
constructor takes care of that.
When a transaction after the initial deposit goes through, the balance should be updated at that time. Then we can refactor the convoluted getBalance()
into a simple getter:
public CurrencyAmount getBalance() {
return this.balance;
}
Of course CurrencyAmount
, or whatever we’re using in its place, ought to be immutable, meaning that the only way to change a CurrencyAmount
object is by instantiating a new CurrencyAmount
and changing the pointer.
This way, the caller can do something like
CurrencyAmount amount = someAccount.getBalance();
System.out.println(amount.plus(someOtherAmount));
without our having to worry that someAccount.balance
has been changed unwittingly. Marking balance
as private and the CurrencyAmount
class being immutable ensures that the only way to change balance
is by an Account
object reassigning its balance
field, e.g.,
this.balance = this.balance.plus(transaction.getAmount());
This is also not because we’re worried about hackers changing the account balance somehow, but to prevent mistakes we might make ourselves, such as changing the account balance without logging a corresponding transaction.
Maybe that’s enough for the Account
class today. Just for the sake of completeness, though, the class’s closing curly brace.
}
The frameworks and other third-party libraries that we use sometimes mandate specific access modifiers.
For example, JUnit 4 requires test procedures annotated with @Test
to be public. Furthermore, test classes must be public and they must have public constructors (the default constructor will do just fine in most cases).
Failure to mark these as public will consistently cause InitializationError
(which, somewhat confusingly, is a subclass of Exception
, not Error
).
JUnit 5 changed to allow package private access for tests. I think that if you’re using JUnit 5, you should definitely restrict access to package private if there’s no good reason for a test to be public.
The only good reason I can think of is for tutorial writers wanting to be version agnostic, since, at least for the time being, a lot of Java programmers haven’t yet made to switch to JUnit 5.
Considerations for interfaces
Like classes, interfaces can be public or package private. As it turns out, Java 8 allows only public access for all interface members, even if the interface itself is package private.
This means that implementing classes must mark implementations of “mandatory methods” from interfaces as public regardless of the interface’s access modifier. Otherwise, you’ll get an error about trying to assign narrower access privileges than the interface dictates.
The only limitation of a package private interface is that its implementing classes must be in the same package. Then those implementations will be available outside of the package if the implementing class is public.
Therefore, up to Java 8, it’s unnecessary to mark an interface function or procedure as public. So IntelliJ will also give a warning for it, and offer to remove the “redundant” modifier for you.
The ability to define implementations in an interface in Java 8 benefits us even if we don’t write any interfaces with implementations ourselves. But it can also cause conflict between the principles of maintainable software and one of the principles from the infamous SOLID acronym.
For example, let’s say we write a comparator that sorts Account
objects in a Java collection (like ArrayList<Account>
) by date of the initial deposit (remember that each Transaction
object should have a date field), starting with the oldest account and ending with the most recently opened account.
So we write a class that implements Comparator<Account>
. Then let’s say we need a similar comparator, also going by date of initial deposit, but with the most recent first and the oldest last.
With Java 7 that would have meant writing another class that implements Comparator<Account>
. But with Java 8, we can just use the default implementation of reversed()
provided by Comparator<T>
.
Oftentimes, a default implementation consists of just one or two very obvious lines, helping us cut down on the subtle duplication that would occur if we couldn’t write default implementations.
But what happens if a default implementation runs twenty lines? Thirty lines? More than that? More lines mean longer units, and more places for mistakes to hide. For the sake of maintainability, units should be short.
In a class, I would advise you to break up the long unit into several private sub-units. You can also do that in a Java 8 interface, but here’s the problem: the sub-units are exposed as public to classes implementing the interface.
A non-abstract class implementing the interface doesn’t have to override units with default implementations. But still, those units present the programmer of the implementing class with a decision to make: “Do I need to override this? What is this even for?”
Breaking up a default implementation into public sub-units feels like a violation of the Interface Segregation Principle from the infamous SOLID acronym, and it definitely clutters up the interface documentation with details we would rather not burden implementers with.
One alternative, to only include default implementations when those default implementations are short enough, is unsatisfactory, as is the other alternative of allowing overly long default implementations.
So Java 9 introduced “private methods” for interfaces. Obviously a private function or procedure can’t be overridden by an implementing class (nor by a sub-interface), so it must have an implementation.
And since it’s obviously the only implementation, it’s trivially the default implementation, and it is therefore unnecessary to write “default
” on the declaration line.
I believe you can still write it if you want, but your IDE will offer to remove it. I believe it wouldn’t cause a compilation error (but I have not actually checked this, so please let me know if this is not the case).
Maybe a lot of programmers, upon seeing a dialog box with checked boxes for the “mandatory methods” and unchecked boxes for the ones that have default implementations, will not question the slate in the slightest. Really, though, you should, even if you decide not to make any changes.
Now let’s revisit the strange case of serialVersionUID
. It seems to be required by the Serializable
interface. Working in IntelliJ IDEA, for example, you might notice that a serialVersionUID
is gray if a class hasn’t been declared as implementing the Serializable
interface, as the following screenshot shows:

But as soon as you write “implements Serializable
” in the class declaration, the syntax highlighting will change to reflect that serialVersionUID
is being used, and the associated warning goes away, along with the offer to remove the unused field.

Notice also that I changed the access from public to private. How can that be allowed, if Serializable
requires serialVersionUID
?
And yet, when we look at the source of Serializable
, after stripping out the license header and the Javadoc, all we’re left with is this:
package java.io;public interface Serializable {}
That’s it, that’s all of it. What’s going on here is that when a class is serialized, its serialVersionUID
field will be accessed by platform-specific subroutines, generally called “native methods” (and if it has no such field, one will be generated for it, which may or may not be as convenient as it sounds).
The fact that the platform-specific subroutines can bypass the access modifiers should not discourage us from choosing the best access modifier from the point of view of the other Java classes in the project.
Since the other Java classes in the project have no use for the serialVersionUID
of a particular serializable class, that field should be private, even though it’s effectively public when you figure in the platform-specific subroutines.
Access modifiers for nested classes
I thought about nesting the transaction classes (Deposit
, Withdrawal
, etc.) into Account
to illustrate nested classes. But I believe that on a real world project, I would much prefer to put the transaction classes in their own subpackage, for the sake of the single responsibility principle.
This means that Deposit
and Withdrawal
will have to be public, so that Account
and its subclasses can use them. Probably even Transaction
should be public, to enable polymorphism outside of the transactions
package.
The best example that I can think of for a nested class is a node in a data structure, such as a singly-linked list or a tree.
However, since the JDK already provides LinkedList<E>
, the singly-linked list exercise is not so appealing (even though java.util.LinkedList<E>
is doubly-linked rather than singly-linked).
Even so, if you’ve never done a data structure exercise, I urge you to at least try your hand at it.
The basic idea of linked lists is simple. Unlike array-lists, linked lists are not backed by arrays. Instead, the list is a chain of nodes with pointers between them. Obviously each node in a singly-linked lists only has one pointer, while a node in a doubly-linked list has two pointers.
It seems obvious that a node’s pointer in a singly-linked list should be to the next element of the list, though I suppose having it point to the previous element of the list might be workable.
Then a sensible design pattern is for a linked list class to enclose a node class. Depending on your JDK version, that’s probably what you will find in the source for LinkedList
.
The node class should be private, because there’s no sense in any other class besides the enclosing class using it.
For example, if in the same project you’re also making a doubly-linked list, your doubly-linked list class might also have its own enclosed node class, which is distinct from the singly-linked list’s node class.
You might think that Node<E>
’s two fields, linking procedure and constructor are all effectively private to SinglyLinkedList<E>
, even though they’re technically package private because of the absence of an access modifier for any of them.
And you would be right. Although it’s certainly possible to leak a Node<E>
instance out of SinglyLinkedList<E>
(it’s a mistake I made once or twice while working through the exercise, leaking to SinglyLinkedListTest
), the recipient of the leaked instance would be unable to do anything with it that’s not defined in Object
.
But, if you prefer, you can certainly mark everything in Node<E>
private; SinglyLinkedList<E>
will still be able to access it all.
In light of this, Node<E>
’s link()
procedure might seem unnecessary. If there’s any validation to be done (e.g., if SinglyLinkedList<E>
does not allow null elements), that’s the responsibility of the enclosing class.
Although “static
” is not an access modifier, in the case of nested classes it does have an impact on access. If a nested class is not marked static, it can access the private fields of the enclosing class.
But there’s almost certainly no good reason for Node<E>
to access any of the SinglyLinkedList<E>
fields. A node doesn’t need to know whether or not it’s the head or tail node, only what node it links to.
It doesn’t cause a compilation error to omit the “static
” modifier in this context, and NetBeans won’t say anything about it. But IntelliJ IDEA will give you a warning when a nested class can be static.
Note also that SinglyLinkedList<E>
has both a public constructor for a new, empty list and a private constructor for a list that is a sub-list of an existing list. The latter is for the benefit of the subList()
function, which is required by the List<E>
interface.
Reading the Javadoc for List<E>
’s subList()
function, I think I may have made the wrong choice of interface for SinglyLinkedList<E>
to implement. All the extra mutability suggested by the interface’s Javadoc sure adds a lot of complexity to the project. But that’s a topic for another day.
The takeaway here is that we don’t burden other programmers using our SinglyLinkedList<E>
class with the details of how an instance of that class maintains its state. Our class is supposed to take care of it automatically.
To summarize
From the Account
example, and the discussion of nested classes, we can formulate the following advice for choosing access modifiers:
- An outer class should only be public if it’s needed outside of its package. But that’s probably most classes.
- An inner class should be private if it’s not needed outside of its enclosing outer class.
- Constants should only be public if they’re needed outside of their class’s package.
- The getters and setters of an outer class should probably have the same access modifier as the class.
- Helper units for a default implementation in an interface should be private (except in Java 8, in which this is not allowed).
- If a framework allows a stricter access than public and there is no other reason for something to be public, you should use the stricter access.
And, in general, if you can’t think of any reason why a particular item shouldn’t be private, make it private.
But perhaps most importantly of all, don’t be afraid to change your mind before the first commit or even before the first release.
By starting with the more restrictive access modifiers, you give yourself leeway to change your mind later. People who use your library might be grateful for something that was private becoming public. They won’t have the same feeling for something public that becomes private.
There’s certainly more that can be written on this topic, but hopefully this article covers everything you’ll need to decide how to choose access modifiers on a real world project.
Couple of notes about Scala access modifiers
There are some differences and subtleties between Java access modifiers and Scala access modifiers, which I wrote about in a separate article.
But the general idea that you should use the most restrictive access modifier that makes sense in a particular situation applies equally well to Scala as it does to Java.
In Scala that will sometimes mean using instance private access rather than class private access.
Scala also has some nice features for package scopes to help you avoid public access when you would prefer something more restrictive.