@Override
    public void invalidateEntries(final BiPredicate<K, V> entryPredicate) {
        LOGGER.trace(() -> buildMessage("invalidateEntries"));
        Objects.requireNonNull(entryPredicate);

        final Cache<K, V> cache = getCache();
        final ConcurrentMap<K, V> cacheAsMap = cache.asMap();

        final List<K> keysToRemove = cacheAsMap.entrySet()
                .stream()
                .filter(entry -> {
                    LOGGER.trace(() ->
                            buildMessage(
                                    "invalidateEntries",
                                    entry.getKey(),
                                    "ID: " + System.identityHashCode(getCache())));
                    return entryPredicate.test(entry.getKey(), entry.getValue());
                })
                .map(entry -> {
                    LOGGER.trace(() -> buildMessage("invalidateEntries", entry.getKey()));
                    return entry.getKey();
                })
                .toList();

        // Doing the remove on the cacheAsMap means if rebuild was called we are still operating on
        // the old cache
        keysToRemove.forEach(cacheAsMap::remove);
    }
Subclasses should implement this method to add to the cacheBuilder as required
and return a cache built from cacheBuilder. The builder already has a
removalNotificationConsumer if applicable and recordStats has been set.
stroom-cache/stroom-cache-impl/src/main/java/stroom/cache/impl/AbstractStroomCache.java