# How to Implement the Visitor Pattern Without Violating Encapsulation in Java

> Learn how to implement the Visitor pattern in Java while preserving encapsulation. Discover techniques to keep fields private and maintain a stable interface for secure visitor interactions.

- Repository: [Ilkka Seppälä/java-design-patterns](https://github.com/iluwatar/java-design-patterns)
- Tags: deep-dive
- Published: 2026-02-27

---

**You can implement the Visitor pattern in Java without breaking encapsulation by keeping element fields private, exposing only the `accept()` method and domain-specific operations, and ensuring visitors interact with elements through a stable interface rather than raw state getters.**

The Visitor pattern enables you to add new operations to object structures without modifying the elements themselves. However, classic implementations often violate encapsulation by forcing elements to expose internal state through public getters. The **iluwatar/java-design-patterns** repository demonstrates a clean alternative that maintains strong encapsulation while preserving the pattern's extensibility benefits.

## Why Standard Visitor Implementations Break Encapsulation

The classic Visitor pattern relies on **double-dispatch**: an element's `accept(Visitor v)` method calls back to `v.visit(this)`. While this decouples operations from elements, it creates a temptation to add getter methods that expose private fields so visitors can read internal state.

Consider this encapsulation-breaking approach:

```java
class Soldier extends Unit {
    private int ammo;
    
    public int getAmmo() { return ammo; } // Exposes internal state
}

class AmmoVisitor implements UnitVisitor {
    @Override
    public void visit(Soldier s) {
        System.out.println("Ammo: " + s.getAmmo()); // Violates encapsulation
    }
}

```

Once visitors depend on raw getters like `getAmmo()`, the internal representation becomes locked into the public API. Refactoring the field type or name breaks all visitors, creating maintenance debt.

## Encapsulation-Preserving Strategies from java-design-patterns

The army-unit hierarchy implementation in the repository shows how to maintain private state while supporting visitor operations. The key is limiting the surface area between elements and visitors to a controlled, semantic API.

### Hide Internal State Behind the accept() Method

In [`src/main/java/com/iluwatar/visitor/Unit.java`](https://github.com/iluwatar/java-design-patterns/blob/main/src/main/java/com/iluwatar/visitor/Unit.java), the abstract base class keeps its `children` array **private** and **final**. The only entry point for external operations is the `accept(UnitVisitor visitor)` method, which traverses the hierarchy without exposing the collection structure:

```java
public abstract class Unit {
    private final Unit[] children;

    public Unit(Unit... children) {
        this.children = children;
    }

    public void accept(UnitVisitor visitor) {
        Arrays.stream(children).forEach(child -> child.accept(visitor));
    }
}

```

Concrete elements like `Soldier` override `accept()` to call `visitor.visit(this)`, then delegate to the parent for recursive traversal. No internal arrays, counters, or states are exposed directly to visitors.

### Provide Domain-Specific Operations Instead of Raw Getters

Rather than exposing private fields through getters, elements should offer semantic methods that describe what the visitor needs to know. For example, instead of exposing `private int ammo` via `getAmmo()`, provide a method like `ammoInfo()` that returns a processed string:

```java
class Soldier extends Unit {
    private int ammo;

    public String ammoInfo() {
        return ammo > 0 ? "Fully stocked" : "Out of ammo";
    }
}

```

This keeps the internal representation hidden while giving visitors the derived data they need to perform their operations.

### Use Visitor Context Objects for Complex Scenarios

When visitors need multiple pieces of information or mutable state, pass a **context object** through the `accept` signature. The element populates the context with derived data rather than exposing its fields. This approach keeps the element's state private while allowing visitors to collect complex information across the hierarchy.

## Source Code Analysis: The Visitor Hierarchy

The repository implements a clean separation between the element hierarchy (`Unit` → `Soldier`/`Sergeant`/`Commander`) and the operation hierarchy (`UnitVisitor` → specific visitors). This design prevents visitors from accessing internal fields while still enabling type-specific operations.

### The Element Structure

[`Unit.java`](https://github.com/iluwatar/java-design-patterns/blob/main/Unit.java) serves as the composite base. [`Soldier.java`](https://github.com/iluwatar/java-design-patterns/blob/main/Soldier.java), [`Sergeant.java`](https://github.com/iluwatar/java-design-patterns/blob/main/Sergeant.java), and [`Commander.java`](https://github.com/iluwatar/java-design-patterns/blob/main/Commander.java) extend this base, each overriding `accept()` to enable double-dispatch without revealing internals:

```java
// src/main/java/com/iluwatar/visitor/Soldier.java
public class Soldier extends Unit {
    public Soldier(Unit... children) { super(children); }

    @Override
    public void accept(UnitVisitor visitor) {
        visitor.visit(this);
        super.accept(visitor);
    }

    @Override
    public String toString() { return "soldier"; }
}

```

Note that `Soldier` exposes no state fields—only the `accept()` method and a `toString()` for identification.

### The Visitor Interface

[`UnitVisitor.java`](https://github.com/iluwatar/java-design-patterns/blob/main/UnitVisitor.java) declares specific visit methods for each concrete element type, avoiding generic object parameters that would require reflection or casting:

```java
// src/main/java/com/iluwatar/visitor/UnitVisitor.java
public interface UnitVisitor {
    void visit(Soldier soldier);
    void visit(Sergeant sergeant);
    void visit(Commander commander);
}

```

### Concrete Visitor Implementation

[`SoldierVisitor.java`](https://github.com/iluwatar/java-design-patterns/blob/main/SoldierVisitor.java) demonstrates how to implement an operation without accessing private state. It only overrides `visit(Soldier)` and provides empty implementations for other types, focusing solely on its domain concern:

```java
// src/main/java/com/iluwatar/visitor/SoldierVisitor.java
@Slf4j
public class SoldierVisitor implements UnitVisitor {
    @Override
    public void visit(Soldier soldier) {
        LOGGER.info("Greetings {}", soldier);
    }

    @Override public void visit(Sergeant sergeant) {}
    @Override public void visit(Commander commander) {}
}

```

## Running the Encapsulated Visitor Example

The [`App.java`](https://github.com/iluwatar/java-design-patterns/blob/main/App.java) entry point demonstrates how clients use the pattern without ever seeing internal element state:

```java
// src/main/java/com/iluwatar/visitor/App.java
public class App {
    public static void main(String[] args) {
        var commander = new Commander(
            new Sergeant(new Soldier(), new Soldier(), new Soldier()),
            new Sergeant(new Soldier(), new Soldier(), new Soldier())
        );
        
        commander.accept(new SoldierVisitor());
        commander.accept(new SergeantVisitor());
        commander.accept(new CommanderVisitor());
    }
}

```

Each visitor traverses the entire hierarchy through the encapsulated `accept()` mechanism, executing only on the element types they care about while remaining ignorant of the internal `children` array structure.

## Extending Operations Without Breaking Encapsulation

The primary benefit of this design is the ability to add new operations while keeping the element classes closed for modification.

To add a new operation, create a class implementing `UnitVisitor`. The new visitor interacts with elements exclusively through their public `accept()` methods and any domain-specific accessors you choose to expose. The existing element hierarchy requires no changes, maintaining the **Open/Closed Principle**.

If you must add a new element type (e.g., `General`), you will need to update `UnitVisitor` to include `visit(General general)`. This is the well-known trade-off of the Visitor pattern: the interface is stable against new operations but requires modification when the element hierarchy changes.

## Summary

- **Keep element fields private** and avoid creating public getters solely for visitor access.
- **Expose only the `accept()` method** as the entry point for external operations, implementing double-dispatch without revealing internal structure.
- **Use domain-specific methods** instead of raw state getters when visitors need element information.
- **Implement specific visitor interfaces** that work with concrete types rather than generic objects, eliminating the need for reflection.
- **Add new operations by creating visitor classes** without modifying existing element code, preserving encapsulation and following the Open/Closed Principle.

## Frequently Asked Questions

### Does the Visitor pattern always violate encapsulation?

No. While naive implementations often force elements to expose internal state through getters, the pattern itself does not require this. By keeping fields private and providing only semantic methods or using context objects, you maintain strong encapsulation while still enabling double-dispatch operations.

### How does the accept() method preserve encapsulation?

The `accept()` method acts as a controlled gateway. It allows the visitor to perform an operation on the element without giving the visitor direct access to private fields. The element decides what to expose—whether through `toString()`, domain-specific methods, or by passing derived data to the visitor—keeping its internal representation hidden.

### What is the trade-off when using Visitor with encapsulation?

The main trade-off is that adding new element types requires updating the visitor interface and all existing visitor implementations. However, adding new operations (visitors) requires no changes to the element hierarchy. This exchanges element extensibility for operation extensibility, which is ideal when the element structure is stable but operations change frequently.

### Can I use reflection to avoid adding getters for visitors?

While reflection allows access to private fields without getters, it severely breaks encapsulation and creates brittle code that fails when internal field names or types change. The java-design-patterns implementation avoids reflection entirely by using type-specific visit methods in the `UnitVisitor` interface, ensuring compile-time safety and encapsulation.