← Spring Boot & Microservices Interview Guide
Chapter 23 min read

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

StrategyWhen to use it
Cache-asideThe default — lazy-loaded on first read, most common pattern
Write-throughStrong consistency is required between cache and source of truth
Write-behindHigh write throughput, consistency can lag slightly
Refresh-aheadAccess 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.

Krishna Kumar Yadav

Krishna Kumar Yadav

Senior Software Engineer building distributed systems at scale. 9+ years across fintech, airline tech, and startups.