Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java access control: private state and package boundaries

Last updated: 29 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

Java access modifiers limit which source-level declarations may refer to a member; they do not replace validation or protect data after it has been returned.

Java 8+. The program uses only JDK classes and runs without a framework.

Expose an operation, not a writable field

A warehouse wallet should not publish its balance as a writable integer. A caller could set it negative or update it without the checks required for a withdrawal. The example keeps the field private and exposes a method that validates the requested debit before modifying state.

The nested example fits in one runnable file, so it is not a proof of cross-package access rules. In a real package boundary, public means accessible where the enclosing type is accessible, package-private means the same package, and private confines direct access to the applicable enclosing declaration context. Protected adds subclass access rules, with restrictions on the qualifying receiver across packages.

Package-private is the absence of a modifier; it is not a keyword named default. A top-level class can be public or package-private, while nested declarations have more choices. Keep an implementation type package-private when clients only need an interface. That reduces the surface that later refactoring must preserve.

Returning a reference can widen the boundary

A private List field stops direct field access. A public getter returning that same mutable list still allows callers to change its elements. A view or copy policy is needed for mutable state, including nested values. The collection views lesson separates those choices.

Access is also not authentication. A public method can be called by code that can reach it; deciding whether an end user may withdraw funds belongs to an application authorization boundary. The sample is about preserving a numeric invariant inside one process. It deliberately makes no security claim about reflection, native code or untrusted code execution.

Working program

Java
public class WalletBoundary {
    static class WarehouseWallet {
        private int credits;
        WarehouseWallet(int initialCredits) {
            if (initialCredits < 0) throw new IllegalArgumentException("Negative balance");
            credits = initialCredits;
        }
        public boolean debit(int amount) {
            if (amount <= 0) throw new IllegalArgumentException("Positive debit required");
            if (amount > credits) return false;
            credits -= amount;
            return true;
        }
        public int balance() { return credits; }
    }
    public static void main(String[] args) {
        WarehouseWallet wallet = new WarehouseWallet(40);
        System.out.println(wallet.debit(12));
        System.out.println(wallet.debit(50));
        System.out.println("balance=" + wallet.balance());
    }
}

Output

Output
true
false
balance=28

Cost and failure boundaries

The field reads and single-threaded debit operation take O(1) work. No container copy is required because the exposed balance is a primitive value. If two workers share this instance, private does not make check-and-update atomic; use a lock or another ownership rule before sharing it.

Rejected withdrawals leave the old balance intact. That is a useful failure boundary: validation runs before mutation. Test zero, negative, exact-balance and excessive debits. A multi-account transfer needs a wider invariant than either account alone, so two individually valid debit/credit methods do not establish an atomic transfer.

Common Mistakes

  • Do not equate private with thread safety.
  • Do not publish a mutable object and assume a private field still protects its contents.
  • Do not use a same-package example to infer every protected cross-package rule.

Connect the contracts

Compare the boundary explained in Object invariants with the assumptions made by this program.

java
access-control
Storage details