JAVA FOUNDATIONS · JAVA 17
Java encapsulation: protect a wallet balance
Keep state private and expose operations that preserve a simple, testable invariant.
Forsk Coding School learning resources · Updated 2026-10-26
What you will learn
- Explain why private state is useful
- Preserve a non-negative balance
- Distinguish a rejected operation from a successful state change
Before you start: Classes, constructors, conditions and methods that return values.
Start with the rule the object must preserve
The wallet in this lesson stores whole credits, not real money. Its invariant is that the balance must never be negative. If balance were a public field, a caller could assign -100 directly and bypass every rule. Making the field private limits where it can be changed. The class can then check every operation that affects that state.
Private fields alone do not create good encapsulation. A public setBalance method that accepts any integer would reopen the same problem. Expose operations that describe the domain action instead: here a caller can ask to spend a positive number of credits and read the resulting balance.
Choose clear success and failure behaviour
The constructor rejects a negative opening balance by throwing IllegalArgumentException. The spend method uses a different policy: it returns false for an invalid or unaffordable amount and leaves the state unchanged. Either policy can be valid in an application, but callers need an explicit contract. Do not mutate state first and try to repair it after detecting failure.
The successful path subtracts only after both checks pass. Testing amount <= 0 prevents a negative spend from increasing the balance. Testing amount > balance prevents overspending. Spending the exact balance is allowed and leaves zero. These small boundary decisions are part of the object's behaviour, not incidental implementation details.
Test the invariant across a sequence
An object often receives several calls over its lifetime. Test a successful spend followed by an unaffordable spend, then inspect the balance. Checking only the boolean return is insufficient: a faulty method might return false after it has already changed the balance. Verify the state as well as the outcome.
This class is intentionally small and single-threaded. It is not a banking system and does not solve persistence or concurrent updates. Its purpose is to show one protected invariant. As requirements grow, add the necessary guarantees deliberately rather than assuming that the private keyword makes every operation safe in every environment.
Run the worked example
Save as Main.java. Run javac Main.java, then java Main.
class Wallet {
private int balance;
Wallet(int openingBalance) {
if (openingBalance < 0) throw new IllegalArgumentException("Negative balance");
balance = openingBalance;
}
boolean spend(int amount) {
if (amount <= 0 || amount > balance) return false;
balance -= amount;
return true;
}
int getBalance() { return balance; }
}
public class Main {
public static void main(String[] args) {
Wallet wallet = new Wallet(100);
System.out.println(wallet.spend(30));
System.out.println(wallet.spend(80));
System.out.println(wallet.getBalance());
}
}Expected result
true
false
70- The constructor establishes a valid initial state.
- Spending 30 succeeds and leaves 70 credits.
- Spending 80 fails before mutation, so 70 remains.
Common mistakes to check
- A setter that accepts any value can defeat a private-field invariant.
- Reject negative spends so subtracting a negative number cannot add credits.
- Check that failure leaves the balance unchanged.
Try it yourself
Create a Counter with private int value starting at 0. A method increment increases it by one, and getValue returns it. Call increment twice and print 2.
Give me a hint
Do not expose a setter; keep the state update inside increment.
Compare with one working solution
class Counter {
private int value;
void increment() { value++; }
int getValue() { return value; }
}
public class Main {
public static void main(String[] args) {
Counter counter = new Counter();
counter.increment();
counter.increment();
System.out.println(counter.getValue());
}
}Expected result
2Quick check
Choose an answer, then check the explanation. Your answer stays in this page and is not saved as a student record.
Keep learning
Explain the result in your own words, change one input and predict the new output. For additional structured assignments, explore the workbook sample before requesting the full pack.
Explore the practice workbookReference documentation
Use the official reference to check language details. The explanations, examples and exercises on this page are written for this learning resource.
