Spring Boot Advanced Topics Interview Questions
Spring Boot caching strategies — annotations, providers, and the cache-aside vs write-through trade-offs — plus distributed tracing in a microservices context.
Q9. Explain Spring Boot caching in detail. What are the different strategies?
1. Enable caching
@SpringBootApplication
@EnableCaching
public class Application {}
2. Cache annotations
@Service
public class UserService {
@Cacheable(value = "users", key = "#id")
public User getUser(Long id) {
// called only on a cache miss
return userRepository.findById(id);
}
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user); // always writes through, updates cache
}
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id); // removes from cache
}
@CacheEvict(value = "users", allEntries = true)
public void clearAll() {
// clears the entire cache region
}
}
3. Cache providers
Redis, for a distributed cache shared across instances:
spring.cache.type=redis
spring.redis.host=localhost
spring.cache.redis.time-to-live=600000
Caffeine, for a high-performance in-process cache on a single instance:
spring.cache.type=caffeine
spring.cache.caffeine.spec=maximumSize=500,expireAfterWrite=600s
4. Strategies, and when each one actually fits
| Strategy | When to use it |
|---|---|
| Cache-aside | The default — lazy-loaded on first read, most common pattern |
| Write-through | Strong consistency is required between cache and source of truth |
| Write-behind | High write throughput, consistency can lag slightly |
| Refresh-ahead | Access patterns are predictable enough to pre-warm before expiry |
The follow-up question worth being ready for: cache a value only when the underlying operation is genuinely expensive, set a TTL that matches how stale the data is allowed to get, and watch the hit rate — a cache running below roughly 80% hit rate is usually costing more in complexity than it's saving in load.
Q10. How do you handle distributed tracing in Spring Boot microservices?
Use Spring Cloud Sleuth + Zipkin for distributed tracing.
1. Dependencies
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
2. Configuration
spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.probability=1.0 # 100% sampling
3. Automatic Instrumentation
Sleuth automatically adds:
- Trace ID: Unique for entire request chain
- Span ID: Unique per service call
4. Log Output
[service-name,traceId,spanId] INFO UserController - Processing
// Example:
[user-service,a1b2c3d4,span123] INFO Processing user request
5. Custom Spans
@Service
public class UserService {
@Autowired
private Tracer tracer;
public void process() {
Span span = tracer.nextSpan().name("custom-op");
try (Tracer.SpanInScope ws = tracer.withSpan(span.start())) {
span.tag("userId", "123");
// Business logic
} finally {
span.end();
}
}
}
6. Zipkin UI
docker run -d -p 9411:9411 openzipkin/zipkin
# Access: http://localhost:9411
The detail worth volunteering is that trace IDs show the request path, while span timing shows where latency accumulated; collecting traces without sampling and retention controls can become an expensive logging system.
Q21. Explain Spring Boot Profiles vs Spring Cloud Config Server
Spring Boot Profiles:
- Local configuration per environment
- Packaged with application
- Requires rebuild for changes
Spring Cloud Config Server:
- Centralized configuration
- External to application
- Dynamic refresh without rebuild
- Version controlled (Git)
Config Server Setup:
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {}
server.port=8888
spring.cloud.config.server.git.uri=https://github.com/company/config-repo
Client Usage:
spring.application.name=user-service
spring.cloud.config.uri=http://localhost:8888
spring.cloud.config.profile=prod
Dynamic Refresh:
@RefreshScope
@RestController
public class UserController {
@Value("${app.message}")
private String message; // Refreshed when /actuator/refresh called
}
Profiles are sufficient when configuration is small and changes with deployments, while Config Server earns its operational cost when many services need centrally versioned settings and controlled refresh. It also becomes another dependency to secure and monitor, so centralization is not automatically better.
Q22. How do you implement Database Migration with Flyway/Liquibase?
Flyway:
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
src/main/resources/db/migration/
V1__Create_users_table.sql
V2__Add_email_column.sql
V3__Create_orders_table.sql
-- V1__Create_users_table.sql
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Configuration:
spring.flyway.enabled=true
spring.flyway.baseline-on-migrate=true
spring.flyway.locations=classpath:db/migration
Applied migrations should be treated as immutable history: use semantic versioning, test each migration in a production-like environment before rollout, and make operations safe to retry where the tool and database allow it. The follow-up interviewers usually want is how you handle a failed migration without leaving application and schema versions incompatible.