How to Preheat Cache During Spring Startup – Interview Guide
The article explains four ways to preheat caches in Spring during startup, recommends CommandLineRunner/ApplicationRunner as the safest option, and discusses timing, async handling, error protection, and distributed‑lock strategies to avoid common pitfalls in production environments.
Interview Focus
Spring lifecycle understanding – interviewers check whether you know the extension points in the Spring container startup and can place cache‑preheat logic at the correct moment.
Cache‑preheat experience – they expect concrete examples of where the data comes from, how failures are handled, and how long‑running preheat is mitigated.
Solution selection ability – multiple ways exist; you should be able to choose the appropriate one for a given scenario and explain the trade‑offs.
Implementation Options
@PostConstruct(JSR‑250 annotation) – executed after the bean’s own initialization and dependency injection. CommandLineRunner / ApplicationRunner (Spring Boot interfaces) – executed after the entire Spring container and embedded web server are fully started. ApplicationListener<ContextRefreshedEvent> (Spring event) – triggered when the context refresh event is published (after all beans are initialized). InitializingBean (Spring interface) – called after bean property setting is complete.
Execution Timing Analysis
Step 3 – @PostConstruct : runs per bean immediately after that bean is initialized. Safe only when the preheat logic depends solely on resources injected into the same bean.
Step 4 – InitializingBean : timing similar to @PostConstruct but requires implementing an interface, which is more intrusive.
Step 6 – ContextRefreshedEvent : fires after all beans are initialized. In Spring MVC, parent‑child containers may cause the event to fire twice.
Step 8 – CommandLineRunner / ApplicationRunner : the latest point; the container and web server are fully ready. This is the safest and most recommended approach for production.
Method 1 – @PostConstruct
Simple and suitable for lightweight preheat logic.
@Service
public class DictCacheService {
@Autowired
private DictMapper dictMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@PostConstruct
public void preloadDictCache() {
List<Dict> dictList = dictMapper.selectAll();
for (Dict dict : dictList) {
String key = "dict:" + dict.getType() + ":" + dict.getCode();
redisTemplate.opsForValue().set(key, dict.getLabel());
}
log.info("Dictionary cache preheat completed, loaded {} entries", dictList.size());
}
}Each bean triggers @PostConstruct independently, so execution order is not guaranteed. If the preheat logic depends on another bean’s initialization, this method may cause problems.
Method 2 – CommandLineRunner / ApplicationRunner (Recommended)
Provided by Spring Boot; runs after the container is fully started, ensuring all beans are ready.
@Component
public class CachePreheatRunner implements CommandLineRunner {
@Autowired
private UserService userService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public void run(String... args) throws Exception {
log.info("Starting cache preheat...");
// Preheat user info cache
List<User> hotUsers = userService.getHotUsers();
for (User user : hotUsers) {
String key = "user:" + user.getId();
redisTemplate.opsForValue().set(key, user, 2, TimeUnit.HOURS);
}
// Preheat system config cache
Map<String, String> configs = userService.getSystemConfigs();
redisTemplate.opsForHash().putAll("sys:config", configs);
log.info("Cache preheat completed");
}
}The two interfaces differ only in the parameter type: CommandLineRunner receives the raw String[] args, while ApplicationRunner receives an ApplicationArguments object that makes parsing --key=value arguments easier. Execution timing is identical.
Order of multiple runners can be controlled with @Order:
@Component
@Order(1) // Preheat basic data first
public class DictCacheRunner implements CommandLineRunner { ... }
@Component
@Order(2) // Preheat business data next
public class BizCacheRunner implements CommandLineRunner { ... }Method 3 – ApplicationListener<ContextRefreshedEvent>
@Component
public class CachePreheatListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private ConfigService configService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// In Spring MVC this event may fire twice due to parent/child containers
if (event.getApplicationContext().getParent() == null) {
log.info("Context refreshed, starting cache preheat...");
preloadCache();
}
}
}Production Pitfalls & Practices
Long preheat time slows startup : use asynchronous preheat (e.g., annotate a runner method with @Async) to avoid blocking the main thread.
Preheat failure must not block startup : wrap preheat logic in a try‑catch block, log errors, and let the application start; subsequent requests will gradually rebuild the cache.
Duplicate preheat in a cluster : use a distributed lock (e.g., Redis SET NX) or a Kubernetes initContainer so only one instance performs the preheat. The operation is usually idempotent, so occasional duplication only wastes resources.
Async Example
@Component
public class CachePreheatRunner implements CommandLineRunner {
@Autowired
private CachePreheatService cachePreheatService;
@Async
@Override
public void run(String... args) {
cachePreheatService.preheat();
}
}Failure‑tolerant Example
@Override
public void run(String... args) {
try {
preloadCache();
} catch (Exception e) {
log.error("Cache preheat failed, application started normally; subsequent requests will rebuild cache", e);
}
}Distributed‑lock Example
@Override
public void run(String... args) {
String lockKey = "cache:preheat:lock";
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.MINUTES);
if (Boolean.TRUE.equals(locked)) {
try {
preloadCache();
} finally {
redisTemplate.delete(lockKey);
}
} else {
log.info("Another instance is preheating cache, this instance skips");
}
}High‑Frequency Follow‑up Questions
Relation between cache preheat, cache breakdown, and cache penetration : preheat proactively loads hot data to avoid cold‑start breakdown (i.e., cache breakdown). It does not solve cache penetration, which requires Bloom filters or caching empty values.
Difference between CommandLineRunner and ApplicationRunner : the former receives String... args, the latter receives an ApplicationArguments object that simplifies parsing --key=value. Execution timing is identical.
How to avoid duplicate preheat in multi‑instance deployments : use a Redis distributed lock ( SET NX), a Kubernetes initContainer, or rely on the idempotent nature of preheat and accept occasional duplication.
Summary
Spring provides several cache‑preheat mechanisms. In production, CommandLineRunner (or ApplicationRunner) is the safest because it runs after the entire container and web server are ready, eliminating dependency‑order issues. Remember three key points: use asynchronous preheat to avoid slowing startup, protect against failures so the app still starts, and apply a distributed lock to prevent duplicate work in a cluster.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Architect Handbook
Focused on Java interview questions and practical article sharing, covering algorithms, databases, Spring Boot, microservices, high concurrency, JVM, Docker containers, and ELK-related knowledge. Looking forward to progressing together with you.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
