A simple ConcurrentMap wrapped with a cleaner thread still needs to perform somewhat course locking to expire entries since it doesn't have access to the ConcurrentMap's underlying locks. This may cause a bit of contention, in which case you may as well use a Map under the covers. This is more or less ExpiringMap https://github.com/jhalterman/expiringmap/
. For better concurrency/performance, use Caffeine.