I'm not sure this is the best approach.  Subclassing a mutable type and then overriding all of the mutator methods is risky; when we add a new mutator method we need to remember to also add it to the subclassed unmodifiable type.  :-(

I would prefer to do what we did with ObjectId when we created AnyObjectId and MutableObjectId.  Pull the majority of the code into a new abstract base class that supports read-only operations.  Make the existing DirCache/DirCacheEntry types extend that and define the mutation methods on those.  Define two new Unmodifiable types that also extend the abstract base classes, and make those the unmodifiable variant.

A different approach that I had considered when we were talking about this was to simply leave DirCacheEntry mutable, but make DirCache itself be at least thread-safe against concurrent updates by taking the majority of its state and tossing that into a new private inner State class, which is held by DirCache through a volatile field.  Readers can grab the state, and then read the state safely.  Writers that are updating the entry list via a builder, editor, or by re-reading from disk would create a new state and perform a write to the volatile field to publish it.  This does mean that readers who have a DirCache might observe a DirCacheEntry, and then later reading the same path from the same DirCache might get an entirely different entry... but I think that's OK if they are using the cached read-only DirCache and are expecting it to be current with what is on disk.

Then if a client application absolutely needs to have its own stable DirCache we could offer a copy() method on DirCache that makes a new DirCache instance with the same inner State reference... but now future reads wouldn't impact that new copy.  Oh, and that copy was cheap because its just creating a new DirCache and pointing to the same state array.

We do have to trust applications *not* to edit a DirCacheEntry obtained from such a shared DirCache.  We already do that with ObjectLoaders and getCachedBytes().  But maybe DirCacheEntry is too high-level of an API to trust applications not to mutate something they shouldn't?