skip to content

Give a concrete example of the Visitor pattern in the JDK and explain how it works there.

level: juniorimportance: nice to knowfreq 35%

answer

  1. Files.walkFileTree + FileVisitor
  2. Callbacks: preVisitDirectory / visitFile / postVisitDirectory
  3. FileVisitResult steers the walk (CONTINUE/SKIP/TERMINATE)
  4. SimpleFileVisitor base class
  5. javax.lang.model = true accept/visit double dispatch

basics

~20 s

The NIO file API uses it: Files.walkFileTree takes a FileVisitor. As the JDK walks a directory tree, it calls your visitor's methods (visitFile, preVisitDirectory, etc.) for each entry, so you supply the operation and the JDK supplies the traversal.

solid answer

~40 s

The clearest JDK example is java.nio.file.FileVisitor used by Files.walkFileTree. You implement FileVisitor (or extend SimpleFileVisitor) with callbacks: preVisitDirectory, visitFile, visitFileFailed, postVisitDirectory. Files.walkFileTree owns the traversal of the directory tree (the object structure) and calls your visitor at each node, returning a FileVisitResult (CONTINUE, SKIP_SUBTREE, TERMINATE) to steer the walk. This is Visitor in spirit: the structure traversal is decoupled from the operation, and you can add a new operation (delete tree, copy tree, search) just by writing a new visitor without changing the walking code. It isn't textbook double dispatch over a sealed element hierarchy — the elements are uniform Path/attributes — but it embodies the same separation-of-operation-from-structure intent. Other examples: AnnotationValueVisitor / ElementVisitor / TypeVisitor in javax.lang.model (annotation processing), which are true double-dispatch visitors with accept().

go deeper

for a junior

Can name Files.walkFileTree/FileVisitor and describe that the JDK walks the tree and calls your callbacks.

for a middle

Explains the callbacks and FileVisitResult control flow, and that operations are added without changing the walker.

for a senior

Distinguishes the practical FileVisitor (no accept) from the strict javax.lang.model visitors (true double dispatch).

for a principal

Discusses why the JDK chose a loosened visitor for uniform Path nodes vs strict double dispatch for the heterogeneous language-model element hierarchy.

## Why look at JDK examples Seeing a pattern in the standard library proves it isn't academic. The JDK has both a *practical, loosened* Visitor and a *textbook, strict* one. ## Example 1 — NIO file tree walking (practical Visitor) `java.nio.file.Files.walkFileTree(Path start, FileVisitor<? super Path> visitor)` walks a directory tree. You provide a **FileVisitor** — an object that packages the *operation* you want performed at each entry: ```java interface FileVisitor<T> { FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs); FileVisitResult visitFile(T file, BasicFileAttributes attrs); FileVisitResult visitFileFailed(T file, IOException exc); FileVisitResult postVisitDirectory(T dir, IOException exc); } ``` The JDK owns the **object structure** (the file system tree) and the **traversal**; *you* own the **operation** (what to do at each file/dir). Each callback returns a **FileVisitResult** enum that *steers* the walk: `CONTINUE`, `SKIP_SUBTREE`, `SKIP_SIBLINGS`, `TERMINATE`. `SimpleFileVisitor<T>` is a convenience base class with no-op defaults so you override only what you need. This is the **Visitor intent**: separate the operation from the structure, so new operations (delete a tree, copy a tree, find big files) are new visitor classes with **zero changes** to the walking engine. It is *not* the strict GoF form — there's no `accept()` and no per-type `visit` overloads, because every node is a uniform `Path` rather than a sealed family of distinct element classes. ```java Files.walkFileTree(start, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes a) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException { Files.delete(dir); return FileVisitResult.CONTINUE; } }); ``` ## Example 2 — javax.lang.model visitors (textbook Visitor) The annotation-processing API (used by tools and libraries that read your source at compile time) has **true** double-dispatch visitors: `ElementVisitor`, `TypeVisitor`, `AnnotationValueVisitor`. Each model element has an `accept(Visitor, P)` method, and the visitor has one `visitXxx` method per element kind (`visitType`, `visitExecutable`, `visitVariable`, …). Convenience base classes like `SimpleElementVisitor14` supply defaults. This is the strict element-hierarchy + accept/visit form. ## Other honorable mentions - Older Swing/AWT and `java.util.regex` internals use visitor-like dispatch. - DOM/XML `NodeVisitor`-style APIs in many libraries follow the same shape. ## Takeaway When you see an API where *you hand it a callback object and it drives the traversal*, you're usually looking at Visitor (or its close cousin). `FileVisitor` is the one most interviewers expect you to name; `javax.lang.model`'s visitors are the strict, accept-based form to cite when asked for genuine double dispatch in the JDK.

  • How do you stop a file tree walk early?
    Return FileVisitResult.TERMINATE from a visitor callback; return SKIP_SUBTREE to skip a directory's children, or SKIP_SIBLINGS to skip the rest of the current directory.
  • Is FileVisitor a strict GoF Visitor?
    No — it captures the intent (operation separated from traversal) but lacks accept()/per-type visit overloads because all nodes are the same Path type. javax.lang.model's ElementVisitor is the strict, double-dispatch form.

saying these in an interview costs you the question

  • Claiming FileVisitor uses accept()/double dispatch (it doesn't — nodes are uniform Path)
  • Forgetting to return a FileVisitResult from callbacks
  • Not naming any concrete JDK example when asked
  • Confusing FileVisitor with the Iterator pattern

context