0Pricing
Spring Boot 4 Complete Guide · Lección

Caché en memoria con ajuste de Caffeine

Configure las políticas de expulsión, expiración y tamaño de Caffeine para cachés locales de alto rendimiento.

Caché en memoria con ajuste de Caffeine es una lección gratuita de Spring Boot 4 Complete Guide en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Spring Boot 4 Complete Guide, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Spring Boot 4 Complete Guide incluye 4 lecciones en total.

Partes de esta lección aún no han sido traducidas y se muestran en inglés.

Why Caffeine for Local Caches

Caffeine is a high-performance, near-optimal Java caching library and the default in-memory cache for Spring Boot when it is on the classpath.

  • It uses the Window TinyLFU eviction policy, which beats plain LRU on real-world hit rates.
  • It supports size-based, time-based, and reference-based eviction.
  • It is fully concurrent and lock-free on the read path, ideal for high-throughput services.

In this lesson you will tune Caffeine's eviction, expiry, and size policies so a local cache stays fast without exhausting heap.

Adding Caffeine to a Spring Boot 4 App

Spring Boot auto-configures Caffeine when both spring-boot-starter-cache and the caffeine dependency are present.

Then enable caching with @EnableCaching on a configuration class. Methods annotated with @Cacheable will use the Caffeine-backed CacheManager.

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableCaching
public class CacheConfig {
    // CaffeineCacheManager is auto-configured
    // when com.github.ben-manes.caffeine:caffeine is on the classpath
}

Maximum Size Eviction

The most common policy for a local cache is bounded size. Use maximumSize to cap the number of entries; Caffeine evicts the least valuable entries (Window TinyLFU) once the bound is exceeded.

  • Pick a size that fits comfortably in heap given your value object footprint.
  • Eviction is not immediate at the boundary; it happens promptly but asynchronously.
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;

@Bean
public CaffeineCacheManager cacheManager() {
    CaffeineCacheManager manager = new CaffeineCacheManager("products");
    manager.setCaffeine(Caffeine.newBuilder()
            .maximumSize(10_000)
            .recordStats());
    return manager;
}

Weight-Based Eviction

When entries vary wildly in size, bound the cache by weight instead of count. Provide a maximumWeight and a Weigher that returns each entry's cost.

  • You cannot combine maximumSize and maximumWeight on the same cache.
  • Weights are computed once at insertion and are not updated afterward.
import com.github.benmanes.caffeine.cache.Caffeine;

Caffeine.newBuilder()
        .maximumWeight(50_000_000) // ~50MB budget
        .weigher((String key, byte[] value) -> value.length)
        .build();

expireAfterWrite vs expireAfterAccess

Time-based expiry comes in two flavors:

  • expireAfterWrite: entry expires a fixed duration after it was created or last replaced. Best for data with a known freshness window (e.g. a price valid for 5 minutes).
  • expireAfterAccess: entry expires a duration after its last read or write. Best for keeping hot data alive and dropping idle entries.

You may combine both; the entry expires when either condition fires first.

import com.github.benmanes.caffeine.cache.Caffeine;
import java.time.Duration;

Caffeine.newBuilder()
        .maximumSize(10_000)
        .expireAfterWrite(Duration.ofMinutes(5))
        .expireAfterAccess(Duration.ofMinutes(2))
        .build();

Configuring via application.properties

For a single shared spec, Spring Boot lets you skip Java config and use the spring.cache.caffeine.spec property. It accepts the comma-separated Caffeine spec string.

This is convenient but applies the same spec to every cache name; per-cache tuning still requires a programmatic CaffeineCacheManager or a custom CacheLoader.

spring.cache.type=caffeine
spring.cache.cache-names=products,prices
spring.cache.caffeine.spec=maximumSize=10000,expireAfterWrite=5m,recordStats

refreshAfterWrite for Stale-While-Revalidate

refreshAfterWrite differs from expiry: instead of removing the entry, it asynchronously reloads it after the duration while still serving the old value. This avoids a latency spike on the first request after staleness.

  • It requires a LoadingCache (a cache built with a CacheLoader).
  • Only one thread triggers the refresh; others keep reading the existing value.
  • Combine with a longer expireAfterWrite as a hard ceiling.
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import java.time.Duration;

LoadingCache<String, String> cache = Caffeine.newBuilder()
        .refreshAfterWrite(Duration.ofMinutes(1))
        .expireAfterWrite(Duration.ofMinutes(10))
        .build(key -> loadFromDatabase(key));

A Standalone Caffeine Demo

Here is a complete, framework-free program demonstrating maximumSize eviction. Insert more entries than the bound and observe that the cache never exceeds its size after cleanup.

This is the kind of micro-benchmark you can run to validate a tuning choice before wiring it into Spring.

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

public class CaffeineDemo {
    public static void main(String[] args) {
        Cache<Integer, String> cache = Caffeine.newBuilder()
                .maximumSize(3)
                .build();

        for (int i = 0; i < 10; i++) {
            cache.put(i, "value-" + i);
        }
        cache.cleanUp(); // force pending eviction work

        System.out.println("Estimated size: " + cache.estimatedSize());
        System.out.println("Get key 9: " + cache.getIfPresent(9));
    }
}

Per-Cache Tuning with Custom Specs

Real services need different policies per cache: a tiny hot lookup table vs a large warm dataset. Subclass or configure CaffeineCacheManager so each name gets its own builder.

One clean approach is registering individual native caches by name on the manager.

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import java.time.Duration;

@Bean
public CaffeineCacheManager cacheManager() {
    CaffeineCacheManager manager = new CaffeineCacheManager();
    manager.registerCustomCache("prices", Caffeine.newBuilder()
            .maximumSize(1_000)
            .expireAfterWrite(Duration.ofSeconds(30))
            .build());
    manager.registerCustomCache("catalog", Caffeine.newBuilder()
            .maximumSize(100_000)
            .expireAfterAccess(Duration.ofHours(1))
            .build());
    return manager;
}

Measuring Hit Rate with recordStats

You cannot tune what you do not measure. Enable recordStats() to expose CacheStats: hit count, miss count, eviction count, and average load penalty.

  • A low hit rate suggests the cache is too small or the keys too cardinal.
  • High eviction with high miss rate means maximumSize is starving the working set.
  • Spring Boot's Micrometer integration publishes these as metrics when stats are on.
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.stats.CacheStats;

public class StatsDemo {
    public static void main(String[] args) {
        Cache<String, Integer> cache = Caffeine.newBuilder()
                .maximumSize(2)
                .recordStats()
                .build();

        cache.put("a", 1);
        cache.getIfPresent("a"); // hit
        cache.getIfPresent("b"); // miss

        CacheStats stats = cache.stats();
        System.out.printf("hits=%d misses=%d rate=%.2f%n",
                stats.hitCount(), stats.missCount(), stats.hitRate());
    }
}

Avoiding Common Tuning Pitfalls

A few traps to watch for under high throughput:

  • Unbounded caches: never build a cache without a size or expiry bound, or you risk an OutOfMemoryError.
  • initialCapacity: set it near the expected steady-state size to avoid resize churn on the read-heavy path.
  • Soft/weak references: softValues() ties eviction to GC pressure, which is unpredictable; prefer explicit size/time bounds for latency-sensitive caches.
  • refreshAfterWrite without a loader: it silently has no effect on a manual cache.

Quick Check: Choosing an Expiry Policy

You cache product prices that the upstream system guarantees are valid for exactly 5 minutes after publication. Reads are frequent but you must never serve a price older than 5 minutes. Which Caffeine policy fits best?

Recap

You tuned Caffeine for high-throughput local caching in Spring Boot 4:

  • Size: maximumSize for uniform entries, maximumWeight + Weigher for variable cost.
  • Time: expireAfterWrite for freshness contracts, expireAfterAccess to keep hot data, combine for both ceilings.
  • Refresh: refreshAfterWrite on a LoadingCache for stale-while-revalidate without latency spikes.
  • Config: spring.cache.caffeine.spec for one shared spec, or registerCustomCache for per-cache tuning.
  • Measure: always enable recordStats() and watch hit rate and eviction count to guide further tuning.

Preguntas frecuentes

¿La lección «Caché en memoria con ajuste de Caffeine» es gratis?

Sí — el texto completo de «Caché en memoria con ajuste de Caffeine» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Spring Boot 4 Complete Guide, actualiza a CoddyKit PRO. El curso de Spring Boot 4 Complete Guide incluye 4 lecciones en total.

¿Qué aprenderé en «Caché en memoria con ajuste de Caffeine»?

Configure las políticas de expulsión, expiración y tamaño de Caffeine para cachés locales de alto rendimiento. Practicas Spring Boot 4 Complete Guide con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Spring Boot 4 Complete Guide?

No se requiere experiencia previa. Spring Boot 4 Complete Guide en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.

¿Cuánto tiempo toma la lección «Caché en memoria con ajuste de Caffeine»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Spring Boot 4 Complete Guide?

Sí. Cada lección de Spring Boot 4 Complete Guide incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Fundamentos de la abstracción de caché de Spring
  2. Caché en memoria con ajuste de Caffeine
  3. Caché distribuida con Redis y TTL
  4. Avalancha de caché, invalidación y coherencia
← Volver a Spring Boot 4 Complete Guide