Spring Boot — Interview Questions
Stack context: This system uses Spring Boot 3.3.0 with Java 21. All six services are Spring Boot applications using Spring MVC (or WebFlux for the gateway), Spring Data JPA, Spring Data Redis, Spring Kafka, Spring Security, Springdoc OpenAPI, Micrometer/Zipkin tracing, and Spring Boot Actuator.
Q1 — What is Spring Boot and how does it differ from plain Spring Framework? junior
Answer: Spring Boot is an opinionated, convention-over-configuration layer on top of Spring Framework that eliminates most boilerplate configuration.
Key differences:
| Feature | Spring Framework | Spring Boot |
|---|---|---|
| Setup | Manual bean definitions, XML or Java config | Auto-configuration via @SpringBootApplication |
| Embedded server | No — deploy WAR to external Tomcat | Yes — embedded Tomcat/Netty via spring-boot-starter-web |
| Dependency management | Manual version coordination | Spring Boot BOM manages compatible versions |
| Actuator | Not included | Built-in /actuator endpoints |
| Properties | Scattered XML configs | Centralized application.yml |
Auto-configuration: Spring Boot scans classpath for known dependencies and configures beans automatically. If spring-data-redis is on classpath, it configures RedisTemplate with defaults from application.yml.
Q2 — What is @SpringBootApplication and what annotations does it compose? junior
Answer:
@SpringBootApplication is a composed annotation combining:
@SpringBootConfiguration: Marks the class as a configuration source (specialization of@Configuration).@EnableAutoConfiguration: Triggers Spring Boot's auto-configuration mechanism — readsMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.@ComponentScan: Scans the package (and sub-packages) of the annotated class for@Component,@Service,@Repository,@Controllerbeans.
@SpringBootApplication // = all three above
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
Tip: @ComponentScan scans the package of the main class. Always place the main class in the root package (e.g., com.ecommerce.order) so all sub-packages are scanned.
Q3 — What is dependency injection and what are the three ways to inject beans in Spring? junior
Answer: Dependency Injection (DI) is a design pattern where an object's dependencies are provided externally rather than created internally — enabling loose coupling and testability.
Three injection styles:
1. Constructor injection (recommended):
@Service
public class OrderService {
private final OrderRepository repo;
private final KafkaTemplate<String, OrderCreatedEvent> kafka;
public OrderService(OrderRepository repo, KafkaTemplate<String, OrderCreatedEvent> kafka) {
this.repo = repo;
this.kafka = kafka;
}
}
2. Setter injection:
@Autowired
public void setRepo(OrderRepository repo) { this.repo = repo; }
3. Field injection (discouraged in production):
@Autowired
private OrderRepository repo;
Why constructor injection is preferred:
- Dependencies are clearly declared.
- Fields can be
final(immutable after construction). - No reflection hacking needed in unit tests — inject mocks directly via constructor.
Q4 — What is the difference between @Component, @Service, @Repository, and @Controller? junior
Answer:
All four are specializations of @Component — they all mark a class for auto-detection and Spring-managed bean creation. The differences are semantic and functional:
| Annotation | Layer | Extra behavior |
|---|---|---|
@Component |
Generic | None — pure marker |
@Service |
Business logic | None — semantic clarity |
@Repository |
Data access | Enables Spring's persistence exception translation (PersistenceExceptionTranslationPostProcessor) |
@Controller |
Web layer (MVC) | Enables request mapping and view resolution |
@RestController |
Web layer (REST) | @Controller + @ResponseBody — returns JSON/XML directly |
Practical effect: @Repository wraps JDBC/JPA exceptions into Spring's unified DataAccessException hierarchy. @Controller integrates with DispatcherServlet.
Q5 — What is Spring's application context and how does bean lifecycle work? junior
Answer:
The ApplicationContext is Spring's IoC container — it manages bean creation, configuration, and lifecycle.
Bean lifecycle:
- Instantiation: Spring calls constructor (or factory method).
- Dependency injection: Properties and constructor args are injected.
- BeanNameAware / BeanFactoryAware: Callbacks if implemented.
@PostConstruct/InitializingBean.afterPropertiesSet(): Post-initialization logic.- Bean is ready — used by application.
@PreDestroy/DisposableBean.destroy(): Cleanup on context close.
@Service
public class ProductCacheWarmer {
@PostConstruct
public void warmCache() {
// runs after all dependencies are injected but before serving requests
}
@PreDestroy
public void cleanup() {
// runs before context shuts down
}
}
Q6 — What is Spring Boot auto-configuration and how do you debug it? junior
Answer:
Auto-configuration classes are loaded from META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. Each class is annotated with @ConditionalOn* annotations that check if the configuration should apply:
@AutoConfiguration
@ConditionalOnClass(RedisTemplate.class) // only if Redis is on classpath
@ConditionalOnMissingBean(RedisTemplate.class) // only if no existing bean
public class RedisAutoConfiguration {
@Bean
public RedisTemplate<Object, Object> redisTemplate(...) { ... }
}
Debugging auto-configuration:
- Run with
--debugflag: prints an auto-configuration report showing matched and unmatched conditions. spring-boot-actuator→/actuator/conditionsendpoint shows the same report at runtime.@SpringBootTest(properties = "debug=true")in test logs the conditions report.
Override: Declare your own @Bean of the same type to prevent Spring Boot's auto-configured one from being created (due to @ConditionalOnMissingBean).
Q7 — What is application.yml and how do you bind properties to a Java class? junior
Answer:
application.yml is the primary configuration file for Spring Boot applications. Properties are organized hierarchically and override Spring's defaults.
Binding to a POJO with @ConfigurationProperties:
app:
order:
max-items: 50
timeout-seconds: 30
payment-topic: order.created
@ConfigurationProperties(prefix = "app.order")
@Component
public class OrderProperties {
private int maxItems;
private int timeoutSeconds;
private String paymentTopic;
// getters/setters or use record
}
Benefits over @Value:
- Type-safe binding with validation (
@Validated,@NotNull,@Min). - IDE auto-completion with
spring-configuration-metadata.json. - Easier to test — inject the POJO directly.
Q8 — What is @Transactional and how does Spring manage transactions? junior
Answer:
@Transactional marks a method (or class) to run within a database transaction. Spring uses AOP proxies to intercept the method call, begin a transaction, execute the method, and commit or rollback based on the outcome.
@Service
public class OrderService {
@Transactional
public Order placeOrder(OrderRequest request) {
Order order = orderRepo.save(new Order(...));
outboxRepo.save(new OutboxEvent(order.getId(), ...)); // same transaction
return order; // commit on return; rollback on unchecked exception
}
}
Key properties:
propagation:REQUIRED(default — join existing or create new),REQUIRES_NEW(always new),NOT_SUPPORTED(suspend).rollbackFor: By default only rolls back onRuntimeExceptionandError. UserollbackFor = Exception.classfor checked exceptions.readOnly = true: Optimization hint — skip dirty checking, use read replica.
This system: OrderService.placeOrder() is @Transactional to atomically write orders + outbox rows.
Q9 — What is Spring Boot Actuator and what endpoints does it expose? junior
Answer: Spring Boot Actuator provides production-ready features for monitoring and managing Spring Boot apps.
Key endpoints (exposed via HTTP at /actuator):
| Endpoint | Description |
|---|---|
/actuator/health |
Application and component health (UP/DOWN) |
/actuator/info |
Application info (git commit, build version) |
/actuator/metrics |
Micrometer metrics (JVM, HTTP, custom) |
/actuator/env |
Configuration properties and environment |
/actuator/beans |
All Spring beans |
/actuator/conditions |
Auto-configuration report |
/actuator/loggers |
View and change log levels at runtime |
/actuator/prometheus |
Prometheus scrape endpoint |
Security: By default only /health and /info are web-exposed. Enable others:
management:
endpoints:
web:
exposure:
include: "health,info,metrics,prometheus"
This system: All services expose /actuator/health for Docker health checks.
Q10 — What is Spring Data JPA and how does it generate queries from method names? junior
Answer:
Spring Data JPA provides repository abstractions over JPA. By extending JpaRepository<Entity, ID>, you get standard CRUD operations. Custom queries are derived from method names using a DSL.
public interface OrderRepository extends JpaRepository<Order, UUID> {
// Generated: SELECT * FROM orders WHERE customer_id = ? AND status = ?
List<Order> findByCustomerIdAndStatus(UUID customerId, OrderStatus status);
// Generated: SELECT * FROM orders WHERE created_at > ? ORDER BY created_at DESC
List<Order> findByCreatedAtAfterOrderByCreatedAtDesc(LocalDateTime since);
// Custom JPQL for complex queries
@Query("SELECT o FROM Order o WHERE o.status = 'PENDING' AND o.createdAt < :cutoff")
List<Order> findStalePendingOrders(@Param("cutoff") LocalDateTime cutoff);
}
Method name keywords: findBy, countBy, existsBy, deleteBy + And, Or, Between, LessThan, GreaterThan, Like, OrderBy, Top, First.
Q11 — What is the N+1 query problem in JPA and how do you fix it? senior
Answer: The N+1 problem occurs when JPA fetches a collection lazily: 1 query to fetch N parent entities, then N more queries (one per parent) to fetch the child collection.
// PROBLEM: 1 query for orders + N queries for each order's items
List<Order> orders = orderRepo.findAll(); // 1 query
for (Order o : orders) {
o.getItems().size(); // N lazy queries — one per order!
}
Solutions:
1. Fetch join (JPQL):
@Query("SELECT DISTINCT o FROM Order o JOIN FETCH o.items WHERE o.status = :status")
List<Order> findWithItems(@Param("status") OrderStatus status);
2. @EntityGraph (declarative):
@EntityGraph(attributePaths = {"items", "items.product"})
List<Order> findByCustomerId(UUID customerId);
3. Batch fetching: @BatchSize(size=20) on the collection — fetches 20 children per query with IN clause.
Detection: Enable spring.jpa.show-sql=true and count SELECT statements, or use p6spy / Hibernate Statistics.
Q12 — What is the difference between @Bean and @Component? junior
Answer:
@Component |
@Bean |
|
|---|---|---|
| Applied to | Class | Method inside @Configuration class |
| Who creates instance | Spring (via classpath scan) | You write the factory method |
| Use when | You own the class | Third-party class or complex initialization needed |
Example:
// @Component — you own the class
@Component
public class OrderValidator { ... }
// @Bean — configuring a third-party library object
@Configuration
public class KafkaConfig {
@Bean
public KafkaTemplate<String, OrderCreatedEvent> kafkaTemplate(ProducerFactory<String, OrderCreatedEvent> pf) {
return new KafkaTemplate<>(pf); // can't annotate KafkaTemplate itself
}
}
Both result in Spring-managed singleton beans (by default). @Bean methods in @Configuration classes are proxied by CGLIB to ensure singleton semantics even when called directly.
Q13 — What is @Profile and how do you use it for environment-specific beans? junior
Answer:
@Profile activates beans only when a specific profile is active. Use it to provide different implementations per environment.
@Service
@Profile("!test")
public class RealPaymentGateway implements PaymentGateway { ... }
@Service
@Profile("test")
public class MockPaymentGateway implements PaymentGateway { ... }
Activation:
application.yml:spring.profiles.active: dev- JVM arg:
-Dspring.profiles.active=prod - Environment variable:
SPRING_PROFILES_ACTIVE=prod
Profile-specific properties: application-dev.yml overrides application.yml when dev profile is active.
This system: application-test.yml disables Kafka auto-startup and uses EmbeddedKafkaBroker in tests.
Q14 — How does Spring Boot handle externalized configuration and what is the property precedence order? senior
Answer: Spring Boot loads configuration from many sources. Priority (higher overrides lower):
- Command-line arguments (
--server.port=9090) SPRING_APPLICATION_JSONenv var (inline JSON)- OS environment variables (
SERVER_PORT=9090) - JVM system properties (
-Dserver.port=9090) application.properties/application.ymlin current directory- Profile-specific properties (
application-prod.yml) in JAR - Default
application.properties/application.ymlin JAR @PropertySourceannotations- Default values in
@Value
Kubernetes/Docker: Inject sensitive config (DB passwords, API keys) via environment variables or Kubernetes Secrets — they override the application.yml defaults without modifying the JAR.
This system: DB credentials and Kafka bootstrap are injected via Docker Compose environment variables, overriding application.yml defaults.
Q15 — What is Spring AOP and how is it used in Spring Boot? senior
Answer: Aspect-Oriented Programming (AOP) allows cross-cutting concerns (logging, transactions, security, caching) to be modularized and applied declaratively without modifying business logic.
Key concepts:
- Aspect: The class containing cross-cutting logic.
- Join point: A method execution point where the aspect can intercept.
- Advice: When the aspect runs:
@Before,@After,@AfterReturning,@AfterThrowing,@Around. - Pointcut: Expression selecting which join points to intercept.
Example — execution time logging:
@Aspect
@Component
public class PerformanceAspect {
@Around("@annotation(com.ecommerce.Monitored)")
public Object logTime(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
log.info("{} took {} ms", pjp.getSignature().getName(), System.currentTimeMillis() - start);
return result;
}
}
Spring uses AOP internally: @Transactional, @Cacheable, @Async, @Retryable, @Secured are all implemented as AOP aspects.
Limitation: Spring AOP only intercepts Spring-managed bean method calls. It cannot intercept private methods or calls within the same bean (self-invocation bypasses the proxy).
Q16 — What is @Cacheable and how does it work with Redis? senior
Answer:
@Cacheable marks a method whose result should be cached. On first call, the method executes and the result is stored in the cache. On subsequent calls with the same key, the cached value is returned without executing the method.
@Service
public class ProductService {
@Cacheable(value = "products", key = "#id", unless = "#result == null")
public ProductDto getProduct(UUID id) {
return productRepo.findById(id).map(this::toDto).orElse(null);
}
@CacheEvict(value = "products", key = "#product.id")
public void updateProduct(Product product) {
productRepo.save(product);
}
}
Redis integration: Configure RedisCacheManager with TTL:
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory).cacheDefaults(config).build();
}
This system: Uses manual RedisTemplate instead of @Cacheable for finer control over serialization and the X-Cache header.
Q17 — What is @Async and what are its pitfalls? senior
Answer:
@Async runs a method in a separate thread (thread pool) so the caller doesn't block.
@Async
@Transactional
public CompletableFuture<Void> sendNotificationEmail(String userId) {
emailClient.send(...);
return CompletableFuture.completedFuture(null);
}
Enable: @EnableAsync on a configuration class.
Pitfalls:
- Self-invocation: Calling an
@Asyncmethod from within the same bean skips the proxy → runs synchronously. - Transaction boundary:
@Asynccreates a new thread outside the caller's transaction context. Use@Transactionalon the async method itself withREQUIRES_NEWif persistence is needed. - Exception handling: Exceptions in async void methods are swallowed unless you configure
AsyncUncaughtExceptionHandler. - Thread pool exhaustion: Default pool is
SimpleAsyncTaskExecutor(creates new threads unboundedly). Always configure aThreadPoolTaskExecutorwith bounded queue.
Q18 — What is Micrometer and how does it integrate with Spring Boot metrics? senior
Answer: Micrometer is a metrics instrumentation library — a vendor-neutral facade over monitoring systems (Prometheus, Datadog, CloudWatch, etc.). Spring Boot auto-configures Micrometer and publishes JVM, HTTP, DB, and Kafka metrics.
Built-in metrics (exposed at /actuator/prometheus):
http.server.requests— request count, latency by endpoint and status codejvm.memory.used— heap and non-heap usagekafka.consumer.records.lag.avg— consumer lagspring.data.repository.invocations— JPA repository call timeshikaricp.connections.active— DB connection pool
Custom metrics:
@Autowired
MeterRegistry registry;
Counter orderCounter = Counter.builder("orders.placed")
.tag("status", "success")
.register(registry);
orderCounter.increment();
This system: Uses Micrometer with Zipkin for distributed tracing. All services include spring-boot-starter-actuator and micrometer-tracing-bridge-otel.
Q19 — What is distributed tracing and how does Zipkin work with Spring Boot? senior
Answer: Distributed tracing tracks a request as it propagates across microservices. Each request is assigned a Trace ID; each service call within the trace is a Span with its own start/end timestamps and metadata.
Spring Boot integration:
micrometer-tracing-bridge-otel: Instruments HTTP requests, Kafka messages, DB queries.opentelemetry-exporter-zipkin: Sends spans to Zipkin.- Auto-propagates
traceparent/b3headers in HTTP requests and Kafka message headers.
management:
tracing:
sampling:
probability: 1.0 # 100% in dev; use 0.1 (10%) in prod
spring:
zipkin:
base-url: http://zipkin:9411
How it works:
- API Gateway receives
POST /orders→ generatestraceId=abc123,spanId=111. - Forwards to order-service with
traceparent: 00-abc123-111-01header. - order-service creates child span
spanId=222, sends Kafka message with trace header. - payment-service picks up trace from Kafka header, creates child span
spanId=333. - All spans visible in Zipkin UI as a timeline for
traceId=abc123.
Q20 — How does Spring Boot Devtools improve developer productivity? junior
Answer: Spring Boot Devtools provides automatic application restart, LiveReload, and development-optimized defaults:
Auto-restart: When classpath files change (compiled classes), Devtools restarts the app using two class loaders — base classloader (unchanged dependencies) and restart classloader (your code). Restart is ~10× faster than a full cold start.
LiveReload: Triggers browser refresh on static resource changes.
Development-optimized defaults:
- Disables template caching (
spring.thymeleaf.cache=false). - Enables full exception stack traces in error pages.
- Remote debugging support.
Exclusions: Devtools is automatically excluded from production JARs (<optional>true</optional> in POM) and doesn't activate when running from a fully packaged JAR.
Q21 — What is the difference between @RestController and @Controller? junior
Answer:
@Controller: Marks a class as a Spring MVC controller. Handler methods return view names (resolved byViewResolver) orModelAndViewobjects. Used for server-side rendered HTML.@RestController:@Controller+@ResponseBody. Handler methods return data objects serialized directly to HTTP response body (JSON/XML viaHttpMessageConverter). Used for REST APIs.
@RestController
@RequestMapping("/api/products")
public class ProductController {
@GetMapping("/{id}")
public ResponseEntity<ProductDto> getProduct(@PathVariable UUID id) {
return ResponseEntity.ok(productService.getProduct(id));
}
}
Without @ResponseBody (or @RestController), returning ProductDto would cause Spring to look for a view named productDto — failing with 404.
Q22 — What is ResponseEntity and when should you use it? junior
Answer:
ResponseEntity<T> gives full control over the HTTP response: status code, headers, and body.
// Return 200 with body
return ResponseEntity.ok(product);
// Return 201 Created with Location header
return ResponseEntity.created(URI.create("/products/" + product.getId())).body(product);
// Return 404
return ResponseEntity.notFound().build();
// Custom headers
return ResponseEntity
.ok()
.header("X-Cache", "HIT")
.body(product);
vs. returning T directly: Returning T directly implies 200 OK with default headers — fine for simple cases. Use ResponseEntity when you need custom status codes, headers (e.g., Location, X-Cache), or conditional responses.
This system: product-service returns ResponseEntity to set X-Cache: HIT/MISS header.
Q23 — What is @ControllerAdvice and how do you build a global exception handler? junior
Answer:
@ControllerAdvice (or @RestControllerAdvice) defines cross-cutting exception handling for all controllers in one place, avoiding repeated try-catch blocks.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(ResourceNotFoundException ex) {
return new ErrorResponse("NOT_FOUND", ex.getMessage());
}
@ExceptionHandler(ValidationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleValidation(ValidationException ex) {
return new ErrorResponse("VALIDATION_ERROR", ex.getMessage());
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleGeneric(Exception ex) {
log.error("Unexpected error", ex);
return new ErrorResponse("INTERNAL_ERROR", "An unexpected error occurred");
}
}
Important: Never expose internal exception messages (stack traces, SQL errors) to clients — security risk (information disclosure). Log internally; return safe, generic messages externally.
Q24 — What is Spring Validation and how does Bean Validation work with Spring Boot? junior
Answer: Spring Boot integrates Jakarta Bean Validation (Hibernate Validator) for declarative input validation.
Annotate DTO:
public class CreateOrderRequest {
@NotNull
@Size(min = 1, max = 50)
private List<@NotNull UUID> productIds;
@Positive
private int quantity;
@Email
private String customerEmail;
}
Enable in controller:
@PostMapping("/orders")
public ResponseEntity<Order> createOrder(@Valid @RequestBody CreateOrderRequest req) {
...
}
If validation fails, Spring throws MethodArgumentNotValidException → handle in @RestControllerAdvice to return 400 with field-level errors.
Custom validators: Implement ConstraintValidator<YourAnnotation, YourType> and annotate with @Constraint(validatedBy = ...).
Q25 — How does Spring Boot handle database migrations? senior
Answer: Spring Boot integrates Flyway and Liquibase for database schema migration management.
Flyway (used in this system):
- SQL migration scripts named
V{version}__{description}.sqlinclasspath:db/migration/. - Flyway tracks applied migrations in
flyway_schema_historytable. - On app startup, Flyway checks for new scripts and applies them in version order.
spring.flyway.enabled=true(auto-configured when Flyway is on classpath).
-- V1__create_orders_table.sql
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- V2__add_outbox_table.sql
CREATE TABLE outbox (
id BIGSERIAL PRIMARY KEY,
event_type VARCHAR(100) NOT NULL,
payload TEXT NOT NULL,
published BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Benefits: Version-controlled schema changes, reproducible DB state across environments, CI/CD integration.
Q26 — What is Spring's event system and how does ApplicationEventPublisher work? senior
Answer: Spring has a built-in event bus for decoupled intra-process communication. Components publish events; listeners react without the publisher knowing who's listening.
Publish:
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher publisher;
@Transactional
public Order placeOrder(OrderRequest req) {
Order order = orderRepo.save(new Order(req));
publisher.publishEvent(new OrderPlacedEvent(this, order.getId()));
return order;
}
}
Listen:
@Component
public class OrderAuditListener {
@EventListener
@Async
public void onOrderPlaced(OrderPlacedEvent event) {
auditLog.record("Order placed: " + event.getOrderId());
}
}
Transactional listeners: @TransactionalEventListener(phase = AFTER_COMMIT) runs only after the publishing transaction commits — prevents sending emails for orders that were rolled back.
vs. Kafka: ApplicationEvents are in-process only. Use Kafka for inter-service communication, Spring events for intra-service decoupling.
Q27 — How do you configure and tune the Spring Boot embedded Tomcat? senior
Answer:
Embedded Tomcat is configured via server.* properties or programmatically via WebServerFactoryCustomizer.
Common tunings:
server:
port: 8083
tomcat:
max-threads: 200 # max request handling threads (default 200)
min-spare-threads: 10 # always-alive threads
accept-count: 100 # queue size when all threads busy
connection-timeout: 20000 # ms to wait for request data
max-connections: 10000 # max concurrent TCP connections
compression:
enabled: true
mime-types: application/json,text/plain
min-response-size: 2048
Thread pool sizing:
- For I/O-bound services (most microservices):
max-threads = 2 × CPU cores × (1 + wait_time/compute_time). - With Java 21 virtual threads, this ceiling largely disappears — virtual threads handle millions of concurrent requests.
This system: Default configuration. With Java 21 virtual threads enabled (spring.threads.virtual.enabled=true), Tomcat tasks use virtual threads — dramatically increasing concurrency.
Q28 — What is the difference between @Scheduled and a Quartz job in Spring? senior
Answer:
@Scheduled |
Quartz Scheduler | |
|---|---|---|
| Setup | @EnableScheduling + annotation |
Separate library, config, DB tables |
| Distributed | No — runs on every instance | Yes — Job store in DB, single execution |
| Persistence | No — lost on restart | Yes — jobs survive restarts |
| Clustering | No | Yes — built-in lock-based clustering |
| Cron support | Yes | Yes |
| Dynamic scheduling | No — fixed at startup | Yes — add/remove jobs at runtime |
@Scheduled is ideal for simple, single-instance tasks:
@Scheduled(fixedDelay = 100)
public void publishOutboxEvents() {
outboxPublisher.publishPendingEvents();
}
This system: Uses @Scheduled for OutboxPublisher (100 ms fixed delay). Acceptable for single-pod deployment. For multi-pod: add a Redis distributed lock to prevent duplicate publishing (covered in Redis Q9).
Q29 — How does Spring Security work and what is the filter chain? senior
Answer:
Spring Security intercepts requests via a chain of SecurityFilterChain filters registered with the servlet container. Filters execute in order on every request.
Key filters (in order):
SecurityContextPersistenceFilter: Loads security context from session.UsernamePasswordAuthenticationFilter: Processes form login.BearerTokenAuthenticationFilter: Extracts and validates JWT bearer tokens.ExceptionTranslationFilter: ConvertsAccessDeniedException→ 403,AuthenticationException→ 401.FilterSecurityInterceptor: Enforces authorization rules (hasRole,hasAuthority).
Configuration (Spring Security 6 / Spring Boot 3):
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable) // REST APIs use JWT, not cookies
.sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
This system: api-gateway uses Spring Cloud Gateway's SessionValidationFilter (custom WebFilter) to validate JWT via Redis, injecting user context headers downstream. Individual services trust X-User-Id headers (no re-validation needed).
Q30 — How do you write integration tests for a Spring Boot service with Testcontainers? senior
Answer: Testcontainers spins up real Docker containers for dependencies (PostgreSQL, Redis, Kafka) in tests, providing production-equivalent behavior without mocking.
@SpringBootTest(webEnvironment = RANDOM_PORT)
@Testcontainers
class OrderServiceIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16")
.withDatabaseName("orders_test");
@Container
static KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.6.1"));
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
}
@Autowired
private TestRestTemplate restTemplate;
@Test
void shouldPlaceOrderAndPublishEvent() {
ResponseEntity<OrderDto> response = restTemplate.postForEntity("/orders", new CreateOrderRequest(...), OrderDto.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
// ... await Kafka message
}
}
@DynamicPropertySource: Injects container-specific connection details into Spring's environment, overriding application.yml at runtime.
This system: All per-service integration tests use this pattern. See e2e-tests module for full 14-container stack tests.