Skip to main content

Week 2 Day 2 — Encapsulation and Access

Goal

Today I want encapsulation as hiding how the object works, not as “add getters.”

Main questions:

  1. What is encapsulation?
  2. What do private, package, protected, and public mean?
  3. Why are public fields a problem?
  4. When are getters justified?
  5. How does this show up in Spring services and entities?

1. Encapsulation

Encapsulation means the object owns its invariants. Callers use a small public API. Fields stay hidden so I can change representation without breaking callers, and so invalid states are hard to construct.

public final class Quantity {
private final int value;

public Quantity(int value) {
if (value <= 0) {
throw new IllegalArgumentException("quantity");
}
this.value = value;
}

public Quantity plus(int n) {
return new Quantity(this.value + n);
}

public int value() {
return value;
}
}

The invariant “quantity is positive” lives in one place. A public int quantity field cannot protect that.

Memory sentence:

Encapsulation keeps the invariant inside the object. Accessors are optional; protection is not.


2. Access modifiers

From most hidden to most open:

ModifierVisible to
privateThe class itself
(package) no modifierClasses in the same package
protectedSame package and subclasses in other packages
publicEveryone

There is no package keyword. Omitting the modifier is package access.

Practical defaults I use:

  • fields: private
  • constructors used by Spring or factories: public (or package for tests in the same package)
  • service methods that are use cases: public
  • helpers: private
  • types I want as API: public; types I want as implementation: package-private

protected is for planned inheritance. If I do not have a subclass story, I do not use it.


3. Getters are not encapsulation

This is not encapsulated:

public class Order {
public List<Line> lines;
}

This is only slightly better:

public class Order {
private List<Line> lines;
public List<Line> getLines() { return lines; }
public void setLines(List<Line> lines) { this.lines = lines; }
}

Callers can still clear the list, add negative quantities, or set lines to null. I published a mutable bag.

Better:

public class Order {
private final List<Line> lines = new ArrayList<>();

public void add(Line line) {
Objects.requireNonNull(line);
lines.add(line);
}

public List<Line> lines() {
return Collections.unmodifiableList(lines);
}
}

The object exposes operations, not its storage.

For DTOs at the HTTP boundary, a record with components is honest: there is no invariant beyond “this is the payload.” Do not pretend a DTO is a rich domain object.


4. Packages as a boundary

com.example.orders.api vs com.example.orders.internal is a design tool.

  • Public types in api are what other packages may use.
  • Package-private types in internal are implementation.
  • Spring component scanning still needs public (or at least instantiable) types for beans it creates, but other application code should depend on interfaces, not on the concrete class, when I want a seam.

Week 3 will make that seam an interface. Encapsulation is the reason the interface is small.


5. Spring connection

Services: fields private final, no setters, public use-case methods. I do not expose the repository.

Controllers: public mapped methods. They should be thin: validate, call the service, map to a response.

Entities: JPA often needs a no-arg constructor and access to fields. I still keep fields private and avoid publishing internal collections. Framework access (field injection by Hibernate) is not the same as public API.

Field injection in Spring:

@Autowired
private OrderRepository orders; // works, and is weakly encapsulated

The field is private, but the object can be created without its collaborator, and I cannot make the field final. Constructor injection keeps encapsulation and invariants: after new (or after Spring constructs), the object is complete.

Memory sentence:

Private fields plus a constructor invariant beat private fields plus @Autowired.


6. Common traps

Trap 1: “I have getters, so I have OOP.”
Getters can leak the same mutable state.

Trap 2: protected fields “for Spring.”
Spring does not need that. Subclasses then depend on storage.

Trap 3: Public DTO fields as a domain model.
Fine as a JSON bag. Not fine as the type that enforces business rules.

Trap 4: Package-private tests that reach into fields with reflection as a habit.
If the test needs the field, the production API is probably wrong, or I should inject a fake through the constructor.


Practice Questions and Answers

Question 1

What is encapsulation?

Answer:

The object hides its representation and exposes a small API that preserves its invariants. Callers cannot put it into an invalid state without going through that API.


Question 2

Why are public fields a problem on a domain type?

Answer:

Any caller can write any value at any time. Validation in the constructor is pointless if the field can change later. I also cannot change the representation without breaking every caller.


Question 3

Does a getter encapsulate a List field?

Answer:

Not if it returns the live list. Callers mutate it. Return an unmodifiable view, a copy, or offer add/remove methods instead.


Question 4

Why does constructor injection fit encapsulation better than field injection?

Answer:

The collaborator is required to construct the object, the field can be final, and tests pass a fake without reflection. Field injection creates a half-built object and hides the dependency.


Question 5

When is a public record with all components acceptable?

Answer:

When the type is a value or DTO: the data is the API, and there is little invariant beyond validation on construction. That is different from a service or an aggregate that must protect a collection of lines.


Memory sentences

Encapsulation keeps the invariant inside the object.

A getter that returns a live list is a public field with extra typing.

Construct a complete object. Do not poke private fields from the container if a constructor will do.

Next: Week 2 Day 3 — Constructors, this, and Initialization