expireAfter(Expiry) does not reliably invoke the removalListener when used with expireAfterUpdate. test-case here: https://github.com/facboy/caffeine-test
as far as I can tell it happens when the Expiry only sets an expiry on expireAfterUpdate. the Node ends up in the TimerWheel with its original MAX_EXPIRY and this is never updated so getExpirationDelay() always returns some value years in the future, and the expiry task is thus also scheduled for years in the future.