micro/go-micro · warning

ErrItemExpired

ErrItemExpired

Error message

item has expired

What it means

ErrItemExpired is a sentinel error returned by Cache.Get when the requested key exists in the cache but its TTL has elapsed. The cache distinguishes expired entries from missing keys so callers can react to staleness differently from absence. It is declared in cache/cache.go:18 alongside DefaultExpiration, which controls when entries expire.

Source

Thrown at cache/cache.go:18

package cache

import (
	"context"
	"errors"
	"time"
)

var (
	// DefaultCache is the default cache.
	DefaultCache Cache = NewCache()
	// DefaultExpiration is the default duration for items stored in
	// the cache to expire.
	DefaultExpiration time.Duration = 0

	// ErrItemExpired is returned in Cache.Get when the item found in the cache
	// has expired.
	ErrItemExpired error = errors.New("item has expired")
	// ErrKeyNotFound is returned in Cache.Get and Cache.Delete when the
	// provided key could not be found in cache.
	ErrKeyNotFound error = errors.New("key not found in cache")
)

// Cache is the interface that wraps the cache.
type Cache interface {
	// Get gets a cached value by key.
	Get(ctx context.Context, key string) (interface{}, time.Time, error)
	// Put stores a key-value pair into cache.
	Put(ctx context.Context, key string, val interface{}, d time.Duration) error
	// Delete removes a key from cache.
	Delete(ctx context.Context, key string) error
	// String returns the name of the implementation.
	String() string
}

// Item represents an item stored in the cache.

View on GitHub (pinned to 24529f1404)

Solutions

  1. Re-fetch the underlying value from the source of truth and re-store it with Cache.Put/Set before retrying Get
  2. Check errors.Is(err, cache.ErrItemExpired) explicitly and treat it as a cache miss rather than a hard failure
  3. Increase the TTL (or use a longer DefaultExpiration) when storing items that should live longer
  4. Prefer Cache.Get with a longer-lived cache layer (e.g. persistent store) behind it for expensive reads

Example fix

// before
v, _, err := cache.Get(ctx, "user:1")
if err != nil { return err }
// after
v, _, err := cache.Get(ctx, "user:1")
if errors.Is(err, cache.ErrItemExpired) || errors.Is(err, cache.ErrKeyNotFound) {
    v = loadUserFromDB(1)
    _ = cache.Put(ctx, "user:1", v, 10*time.Minute)
}
Defensive patterns

Strategy: fallback

Validate before calling

if exp, ok := expiryFromStore(key); ok && time.Now().After(exp) {
    // entry will be reported expired; refresh proactively
    refreshEntry(key)
}

Type guard

func isExpired(err error) bool { return errors.Is(err, cache.ErrItemExpired) }

Try / catch

v, _, err := cache.Get(ctx, key)
if isExpired(err) || errors.Is(err, cache.ErrKeyNotFound) {
    v = reloadFromSource(ctx, key)
    _ = cache.Put(ctx, key, v, ttl)
} else if err != nil {
    return err
}

Prevention

When it happens

Trigger: Calling Cache.Get (or anything built on it) for a key whose stored entry's expiration timestamp is earlier than the current time. Also occurs when an item was stored with DefaultExpiration and the cache's default TTL elapsed.

Common situations: Session or token caches where entries outlive their TTL; long-running workers re-reading cached config that expired between iterations; tests asserting on cached values after advancing clocks or using very short expirations.

Related errors


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/10c2645d32c5a59e. Report an issue: GitHub.