// SPRING BOOT 3.x SENIOR INTERVIEW GUIDE

Spring Boot Interview Mastery
for 10-Year Developers

Comprehensive, production-depth coverage of every Spring Boot topic interviewers ask at senior level. Includes real code snippets, scenario-based questions, common traps, and the exact phrasing that separates good from great candidates.

IoC / DI Auto-Configuration Spring MVC Spring Security Spring Data JPA Transactions AOP Actuator Testing Microservices Performance Scenario Questions
01
Spring Core & IoC Container
// inversion of control · application context · bean lifecycle
Q-01
What is the Spring IoC Container? What are its types?
FoundationalIoC
+
🎯 IntentInterviewers want to see you go beyond "it manages beans" — talk about BeanFactory vs ApplicationContext and WHY one is preferred.

The IoC container is the core of Spring. It's responsible for creating objects (beans), wiring their dependencies, and managing their complete lifecycle. "Inversion of Control" means instead of your code calling new SomeService(), the container creates and injects it for you.

InterfaceDescriptionUse When
BeanFactoryBasic container. Lazy initialization by default. Minimal features.Memory-constrained environments (rarely today)
ApplicationContextSuperset of BeanFactory. Eager init, event publishing, i18n, AOP support.Always use this in production

Common ApplicationContext implementations you'll encounter in interviews:

  • AnnotationConfigApplicationContext — for standalone apps using @Configuration classes
  • ClassPathXmlApplicationContext — legacy XML-based config
  • AnnotationConfigServletWebServerApplicationContext — what Spring Boot uses internally for web apps
🌱 Senior answer: "ApplicationContext extends BeanFactory and adds enterprise-specific features: event publication via ApplicationEventPublisher, MessageSource for i18n, AOP auto-proxying, and ResourceLoader. In Spring Boot, you never instantiate ApplicationContext manually — SpringApplication.run() does it and returns a ConfigurableApplicationContext."
Q-02
Explain the Spring Bean Lifecycle end-to-end.
Senior LevelBean Lifecycle
+
🎯 IntentThis is a classic senior question. They want you to list the full sequence — most candidates only know 3-4 steps. Know all 10.
Step 1
Bean instantiation — container creates instance via constructor or factory method
Step 2
Property injection — setter injection / field injection performed
Step 3
BeanNameAware.setBeanName() — bean gets its name from the container
Step 4
BeanFactoryAware.setBeanFactory() — reference to factory injected
Step 5
ApplicationContextAware.setApplicationContext() — context reference injected
Step 6
BeanPostProcessor.postProcessBeforeInitialization() — pre-init hook
Step 7
@PostConstruct / InitializingBean.afterPropertiesSet() — init methods called
Step 8
BeanPostProcessor.postProcessAfterInitialization() — post-init hook (AOP proxy created here)
Step 9 ✅
Bean is ready to use — handed to requesting code
Step 10 — Context closes
@PreDestroy / DisposableBean.destroy() — cleanup called
Java — Lifecycle Hooks Example
copy
@Component public class OrderService implements InitializingBean, DisposableBean { @Autowired private PaymentClient paymentClient; @PostConstruct public void init() { // Runs after injection. Validate config, warm caches, open connections. paymentClient.connect(); } @Override public void afterPropertiesSet() { // Same as @PostConstruct but via interface — avoid, @PostConstruct is cleaner } @PreDestroy public void cleanup() { // Runs before bean is destroyed. Close connections, flush buffers. paymentClient.close(); } }
⚠️ Key trap: AOP proxies are created at Step 8 (postProcessAfterInitialization). This means if you call a proxied method from @PostConstruct, it won't be intercepted by AOP — the proxy doesn't exist yet at Step 7.
Q-03
What is the difference between @Component, @Service, @Repository, and @Controller?
FoundationalStereotypes
+

All four are specializations of @Component. Functionally they all register the class as a Spring bean. The difference is semantic + one functional addition for @Repository.

@Component
Generic stereotype. Use when none of the others apply. Utility classes, helpers.
// Used for: PasswordEncoder, utils, generic components
@Service
Business logic layer. No functional difference from @Component but conveys intent clearly to the team.
// Used for: OrderService, PaymentService
@Repository
Data access layer. Extra behavior: Spring translates persistence exceptions (e.g., Hibernate) into Spring's DataAccessException hierarchy.
// Used for: UserRepository, ProductDAO
@Controller
Web layer. Works with Spring MVC DispatcherServlet to handle HTTP requests. Used with view templates.
// Used for: MVC controllers, Thymeleaf views
@RestController
= @Controller + @ResponseBody. Every method returns JSON/XML directly instead of view names. Most common for REST APIs.
// Used for: REST API endpoints
The one key difference to always mention: @Repository enables exception translation. Without it, a JPA exception from Hibernate is a raw HibernateException. With @Repository, it becomes a DataAccessException subclass — unified, catchable, and independent of the JPA vendor.
02
Spring Boot Basics
// starters · @SpringBootApplication · embedded server · profiles
Q-04
What does @SpringBootApplication actually do internally?
Mid LevelAnnotation
+

It's a meta-annotation — a convenience wrapper for three annotations:

Java — @SpringBootApplication decomposed
copy
// These three are equivalent to @SpringBootApplication: @Configuration // Marks this as a source of bean definitions @EnableAutoConfiguration // Turns on Spring Boot's auto-config magic @ComponentScan // Scans current package + sub-packages for beans // You can use them separately for fine-grained control: @SpringBootApplication( scanBasePackages = "com.example.specific", // narrow scan scope exclude = {DataSourceAutoConfiguration.class} // exclude unwanted auto-config ) public class MyApp { public static void main(String[] args) { SpringApplication.run(MyApp.class, args); } }
💡 Senior tip: By default, @ComponentScan scans the package of the class it's on and all sub-packages. This is why your main class should be in the root package (e.g., com.example) — so it sees everything under com.example.service, com.example.controller, etc.
Q-05
What are Spring Boot Starters? Why are they important?
FoundationalStarters
+

Starters are curated dependency bundles. Instead of adding 8 individual Maven/Gradle dependencies for a JPA setup, you add one starter that handles all transitive dependencies at compatible versions.

StarterWhat it pulls in
spring-boot-starter-webSpring MVC, Tomcat, Jackson (JSON), validation
spring-boot-starter-data-jpaHibernate, Spring Data, JDBC, transaction mgmt
spring-boot-starter-securitySpring Security, crypto, config support
spring-boot-starter-testJUnit 5, Mockito, AssertJ, MockMvc, Testcontainers
spring-boot-starter-actuatorHealth, metrics, info, environment endpoints
spring-boot-starter-kafkaApache Kafka client + Spring Kafka
🌱 The real value: Version conflicts are the #1 pain in Java projects. Spring Boot's BOM (Bill of Materials) guarantees all starter dependencies are tested together at compatible versions. You just declare spring-boot-starter-parent and get blessed dependency management for free.
Q-06
How do you manage profiles and environment-specific configuration in Spring Boot?
Mid LevelProfilesConfig
+
application-dev.yml / application-prod.yml
copy
# application.yml — common defaults spring: application: name: order-service --- # application-dev.yml spring: config: activate: on-profile: dev datasource: url: jdbc:h2:mem:devdb logging: level: com.example: DEBUG --- # application-prod.yml spring: config: activate: on-profile: prod datasource: url: ${DB_URL} # inject from env variable — never hardcode username: ${DB_USER} password: ${DB_PASSWORD}
Java — Profile-specific beans
copy
@Configuration public class CacheConfig { @Bean @Profile("dev") public CacheManager noOpCacheManager() { return new NoOpCacheManager(); // disabled cache for dev } @Bean @Profile("prod") public CacheManager redisCacheManager(RedisConnectionFactory factory) { return RedisCacheManager.builder(factory).build(); // real Redis for prod } }
  • Activate via CLI: java -jar app.jar --spring.profiles.active=prod
  • Activate via env: SPRING_PROFILES_ACTIVE=prod
  • In tests: @ActiveProfiles("test")
  • Spring Boot 3.x: Prefer application-{profile}.yml files over spring.profiles key in YAML (deprecated)
Q-07
How does Spring Boot's external configuration priority work?
Senior LevelConfig
+

Spring Boot loads config from multiple sources in a specific priority order. Higher in the list overrides lower.

  • Command-line arguments (--server.port=9090) — highest priority
  • SPRING_APPLICATION_JSON env variable (inline JSON)
  • OS environment variables (SERVER_PORT=9090)
  • Java System Properties (-Dserver.port=9090)
  • Profile-specific application-{profile}.yml outside JAR
  • application.yml outside JAR (in ./config/ or ./)
  • Profile-specific application-{profile}.yml inside JAR
  • application.yml inside JAR (classpath) — lowest priority
Production use: Always inject secrets via environment variables (Step 3), never put them in application.yml committed to git. Use Kubernetes Secrets, AWS Parameter Store, or HashiCorp Vault for production secrets management.
Q-08
What is @ConfigurationProperties and how is it better than @Value?
Mid LevelConfig Binding
+
Java — @ConfigurationProperties (preferred)
copy
// application.yml: // payment: // gateway-url: https://pay.example.com // timeout-ms: 5000 // max-retries: 3 @ConfigurationProperties(prefix = "payment") @Validated public class PaymentProperties { @NotBlank private String gatewayUrl; // payment.gateway-url → camelCase mapped @Min(1000) @Max(30000) private int timeoutMs; private int maxRetries = 3; // default value // getters + setters or use Lombok @Data } // Then inject cleanly: @Service public class PaymentService { private final PaymentProperties props; public PaymentService(PaymentProperties props) { this.props = props; } }
Feature@Value@ConfigurationProperties
BindingOne field at a timeEntire prefix group at once
ValidationNo built-inWorks with @Validated, @NotNull, etc.
Type SafetyString parsing, error-proneStrongly typed with POJO
IDE SupportLimitedFull autocomplete with spring-configuration-processor
SpELYesNo (not needed)
RefactoringString keys break silentlyIDE refactoring safe
03
Auto-Configuration
// conditional beans · spring.factories · custom starters
Q-09
How does Spring Boot Auto-Configuration work internally? Trace the mechanism.
Expert LevelAuto-ConfigInternals
+
🎯 IntentThis is a top senior question. Most devs use auto-config without knowing how it works. Interviewers at top firms want the full trace.
Step 1
@SpringBootApplication includes @EnableAutoConfiguration
Step 2
@EnableAutoConfiguration imports AutoConfigurationImportSelector
Step 3 — Spring Boot 2.x
Reads all class names from META-INF/spring.factories under EnableAutoConfiguration key. Loads ~130+ auto-config candidates.
Step 3 — Spring Boot 3.x (new)
Reads from META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (faster, AOT-friendly)
Step 4
Each auto-config class has @Conditional annotations that filter which ones actually apply
Step 5
Surviving configs are imported. Their @Bean methods create beans — but only if conditions pass.
Java — How DataSourceAutoConfiguration works internally
copy
@AutoConfiguration @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) // ↑ Only applies if DataSource class is on the classpath @ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory") // ↑ Only applies if we're not using reactive R2DBC @EnableConfigurationProperties(DataSourceProperties.class) public class DataSourceAutoConfiguration { @Bean @ConditionalOnMissingBean // ← Only creates if YOU haven't defined your own public DataSource dataSource(DataSourceProperties properties) { return properties.initializeDataSourceBuilder().build(); } }
💡 Debug tip: Run with --debug flag or set logging.level.org.springframework.boot.autoconfigure=DEBUG to see the "AUTO-CONFIGURATION REPORT" showing which auto-configs matched and which were excluded and why.
Q-10
What are the main @Conditional annotations? Give real examples of each.
Senior LevelConditional
+
Java — Conditional Annotations in practice
copy
// 1. @ConditionalOnClass — bean created only if class exists on classpath @Bean @ConditionalOnClass(RedissonClient.class) public CacheManager redissonCache() { ... } // 2. @ConditionalOnMissingBean — only if YOU haven't defined your own @Bean @ConditionalOnMissingBean public ObjectMapper jacksonMapper() { return new ObjectMapper(); } // → You can override by declaring your own ObjectMapper @Bean // 3. @ConditionalOnProperty — based on config property @Bean @ConditionalOnProperty(name = "feature.new-payment-flow.enabled", havingValue = "true") public PaymentProcessor newFlowProcessor() { return new NewPaymentProcessor(); } // 4. @ConditionalOnBean — only if another bean exists @Bean @ConditionalOnBean(DataSource.class) public JdbcTemplate jdbcTemplate(DataSource ds) { return new JdbcTemplate(ds); } // 5. @ConditionalOnExpression — SpEL expression @Bean @ConditionalOnExpression("${app.mode} == 'async' and ${feature.kafka.enabled}") public KafkaProducer kafkaProducer() { ... }
AnnotationCondition
@ConditionalOnClassClass exists on classpath
@ConditionalOnMissingClassClass does NOT exist on classpath
@ConditionalOnBeanSpecific bean exists in context
@ConditionalOnMissingBeanNo bean of that type in context (most used)
@ConditionalOnPropertyConfig property exists with value
@ConditionalOnWebApplicationRunning as a web app
@ConditionalOnCloudPlatformRunning on Kubernetes, Cloud Foundry, etc.
@ConditionalOnExpressionSpEL expression evaluates true
04
Beans & Scopes
// singleton · prototype · request · session · circular deps
Q-11
Explain all Spring Bean scopes with a real-world analogy for each.
Mid LevelScopes
+
ScopeInstancesReal World AnalogyUse Case
singleton1 per IoC container (default)Company CEO — one person handles all requestsStateless services, repositories, configuration
prototypeNew instance per requestCoffee cup — every customer gets a fresh oneStateful beans, command objects
requestNew instance per HTTP requestHTTP ticket — new ticket per request, gone after responseRequest-scoped data, correlationId holder
sessionNew instance per HTTP sessionShopping cart — one per user sessionUser-session data in web apps
application1 per ServletContextOffice lobby — shared among all users of the appApp-wide counters, shared state
websocket1 per WebSocket sessionPhone call — lasts duration of the call onlyWebSocket stateful data
🔴 Classic trap: Injecting a prototype-scoped bean into a singleton. The singleton is created once — it captures the prototype at creation time and uses it forever. You effectively get singleton behavior for the prototype. Fix: inject ApplicationContext and call getBean() each time, or use @Lookup method injection.
Java — Correct way to use prototype in singleton
copy
@Component public abstract class OrderService { @Lookup // Spring overrides this to return a new instance each call public abstract OrderContext createOrderContext(); public void processOrder(Long orderId) { OrderContext ctx = createOrderContext(); // fresh prototype each time ctx.setOrderId(orderId); } }
Q-12
What is a Circular Dependency in Spring? How do you fix it? Why is it a design smell?
Senior LevelCircular DepDesign
+

A circular dependency occurs when Bean A depends on Bean B, and Bean B depends on Bean A. Spring Boot 2.6+ throws BeanCurrentlyInCreationException by default (circular dependency detection is on).

Java — Circular Dep problem + 3 fixes
copy
// ❌ PROBLEM — circular dependency: @Service class ServiceA { @Autowired ServiceB b; } @Service class ServiceB { @Autowired ServiceA a; } // → BeanCurrentlyInCreationException at startup // ✅ FIX 1 — @Lazy on one side (Spring creates a proxy) @Service class ServiceA { @Autowired public ServiceA(@Lazy ServiceB b) { this.b = b; } } // ✅ FIX 2 — Setter injection instead of constructor (use sparingly) @Service class ServiceA { private ServiceB b; @Autowired public void setServiceB(ServiceB b) { this.b = b; } } // ✅ FIX 3 — BEST: Refactor. Extract shared logic to a third service. @Service class SharedLogicService { ... } @Service class ServiceA { @Autowired SharedLogicService s; } @Service class ServiceB { @Autowired SharedLogicService s; }
⚠️ Senior perspective: Circular dependencies are a design smell indicating violation of Single Responsibility Principle. The real fix is always refactoring, not @Lazy. When you see a circular dependency, ask: "Are these two classes doing too much? Can shared logic be extracted?" Spring Boot 2.6+ forcing this exception was intentional — to push developers toward better designs.
05
Dependency Injection
// constructor · setter · field · @Qualifier · @Primary
Q-13
Constructor vs Setter vs Field injection — what's the right choice and why?
Senior LevelDI Types
+
Java — Three types compared
copy
// ✅ CONSTRUCTOR INJECTION — preferred (Spring team recommends this) @Service public class OrderService { private final PaymentService paymentService; // final = immutable ✓ private final InventoryService inventoryService; // @Autowired is optional since Spring 4.3 when there's one constructor public OrderService(PaymentService p, InventoryService i) { this.paymentService = p; this.inventoryService = i; } } // ⚠️ SETTER INJECTION — use for optional dependencies @Service public class ReportService { private EmailService emailService; // not final — mutable @Autowired(required = false) // optional dependency public void setEmailService(EmailService e) { this.emailService = e; } } // ❌ FIELD INJECTION — avoid (hard to test, hides dependencies) @Service public class BadService { @Autowired private PaymentService paymentService; // can't be final, can't be mocked without Spring context }
FeatureConstructorSetterField
Immutability✓ final allowed✗ not final✗ not final
Testing (no Spring)✓ plain new()✓ setters✗ needs reflection
Circular detection✓ fails fast✗ silent✗ silent
Optional deps✗ awkward✓ natural fitPartial
Spring recommendation✓ PreferredSometimes✗ Avoid
Q-14
When do you use @Qualifier vs @Primary? Can they conflict?
Mid Level@Qualifier@Primary
+
Java — @Primary and @Qualifier example
copy
@Component @Primary // default when no qualifier specified public class EmailNotificationService implements NotificationService { } @Component public class SmsNotificationService implements NotificationService { } // Injection: @Service public class OrderService { // Gets EmailNotificationService (the @Primary one) @Autowired private NotificationService defaultNotifier; // Explicitly gets SMS service regardless of @Primary @Autowired @Qualifier("smsNotificationService") // @Qualifier wins over @Primary private NotificationService smsNotifier; }
💡 Rule: @Qualifier always beats @Primary. @Primary just sets the default when no qualifier is specified. Use @Primary for "the usual" bean, @Qualifier for explicit override. You can also create custom qualifier annotations (e.g., @SmsChannel) for self-documenting code.
06
Spring MVC & REST
// DispatcherServlet · exception handling · validation · filters
Q-15
Explain the DispatcherServlet request lifecycle from HTTP request to response.
Senior LevelDispatcherServlet
+
HTTP Requestarrives at Tomcat
Filter ChainSecurity, CORS, logging
DispatcherServletFront Controller
HandlerMappingfinds @Controller
HandlerAdapterinvokes method
@Controller methodbusiness logic
ViewResolver / JSONJackson serializes
HttpMessageConverterwrites response
HTTP Responseback to client
  • Filters run before DispatcherServlet — good for authentication, CORS headers, request logging
  • Interceptors run inside DispatcherServlet — access to handler info, pre/post/afterCompletion hooks
  • @ExceptionHandler / @ControllerAdvice catch exceptions after controller execution
  • MessageConverters handle content negotiation — Jackson for JSON, JAXB for XML
Q-16
Design a global exception handling strategy for a Spring Boot REST API.
Senior LevelException HandlingProduction
+
Java — Production-grade Global Exception Handler
copy
@RestControllerAdvice @Slf4j public class GlobalExceptionHandler { // 1. Business logic exceptions (your custom ones) @ExceptionHandler(OrderNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ApiError handleOrderNotFound(OrderNotFoundException ex, HttpServletRequest req) { log.warn("Order not found: {}", ex.getMessage()); return new ApiError("ORDER_NOT_FOUND", ex.getMessage(), req.getRequestURI()); } // 2. Validation failures (Bean Validation / @Valid) @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ApiError handleValidation(MethodArgumentNotValidException ex) { List<String> errors = ex.getBindingResult().getFieldErrors() .stream().map(e -> e.getField() + ": " + e.getDefaultMessage()) .collect(Collectors.toList()); return new ApiError("VALIDATION_FAILED", "Input validation failed", errors); } // 3. Unexpected exceptions — log everything, return generic message @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ApiError handleAll(Exception ex, WebRequest request) { log.error("Unhandled exception", ex); // full stack trace in logs return new ApiError("INTERNAL_ERROR", "An unexpected error occurred"); // ↑ Never expose stack traces or internal info to client! } } // Consistent error response DTO public record ApiError( String code, String message, String path, Instant timestamp, List<String> errors ) { public ApiError(String code, String message) { this(code, message, null, Instant.now(), List.of()); } }
Q-17
How do you implement Bean Validation in Spring Boot? Custom validators?
Mid LevelValidation@Valid
+
Java — Request validation + custom validator
copy
// Request DTO with validation annotations public record CreateOrderRequest( @NotBlank(message = "Customer ID is required") String customerId, @NotEmpty @Size(max = 50) List<@Valid OrderItem> items, @Future(message = "Delivery date must be in the future") LocalDate deliveryDate, @ValidCouponCode // custom validator String couponCode ) {} // Controller — trigger validation with @Valid @PostMapping("/orders") public ResponseEntity<OrderResponse> createOrder(@Valid @RequestBody CreateOrderRequest req) { return ResponseEntity.status(201).body(orderService.create(req)); } // Custom Validator Implementation @Constraint(validatedBy = CouponCodeValidator.class) @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ValidCouponCode { String message() default "Invalid coupon code format"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } @Component public class CouponCodeValidator implements ConstraintValidator<ValidCouponCode, String> { @Override public boolean isValid(String code, ConstraintValidatorContext ctx) { return code == null || code.matches("[A-Z0-9]{6,10}"); } }
07
Spring Security
// SecurityFilterChain · JWT · OAuth2 · method security
Q-18
How has Spring Security config changed in Spring Boot 3? (WebSecurityConfigurerAdapter is removed)
Expert LevelSpring Boot 3Security
+
🎯 IntentMany devs are stuck on Spring Boot 2 patterns. Spring Boot 3 / Spring Security 6 removed WebSecurityConfigurerAdapter. Knowing this shows you're current.
Java — Spring Boot 3 Security (new component-based approach)
copy
// ❌ OLD WAY — Spring Boot 2 (WebSecurityConfigurerAdapter — REMOVED in 3.x) @Configuration public class OldSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { ... } } // ✅ NEW WAY — Spring Boot 3 / Spring Security 6 @Configuration @EnableWebSecurity @EnableMethodSecurity // enables @PreAuthorize, @PostAuthorize public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .csrf(csrf -> csrf.disable()) // stateless APIs → disable CSRF .sessionManagement(s -> s.sessionCreationPolicy(STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**", "/actuator/health").permitAll() .requestMatchers(HttpMethod.GET, "/api/products/**").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) .build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); // cost factor 12 (recommended for 2024) } } // Method-level security @Service public class OrderService { @PreAuthorize("hasRole('ADMIN') or #userId == authentication.name") public List<Order> getOrdersForUser(String userId) { ... } }
Q-19
Implement JWT authentication filter in Spring Boot. What are JWT security pitfalls?
Expert LevelJWTSecurity
+
Java — JWT Filter implementation
copy
@Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtService jwtService; private final UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { String authHeader = req.getHeader("Authorization"); // Skip if no Bearer token if (authHeader == null || !authHeader.startsWith("Bearer ")) { chain.doFilter(req, res); return; } String jwt = authHeader.substring(7); String username = jwtService.extractUsername(jwt); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (jwtService.isTokenValid(jwt, userDetails)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(req)); SecurityContextHolder.getContext().setAuthentication(authToken); } } chain.doFilter(req, res); } }
🔴 JWT Security Pitfalls to always mention: (1) Don't store sensitive data in payload — it's Base64, not encrypted. (2) Use RS256 (asymmetric) over HS256 in microservices so only the auth server holds the private key. (3) Short expiry (15min access, 7d refresh). (4) Implement token revocation via a blacklist (Redis) or use short expiry + refresh token rotation. (5) Always validate alg header — "alg:none" attack.
08
Spring Data & JPA
// repositories · N+1 problem · lazy loading · JPQL · projections
Q-20
What is the N+1 query problem and how do you fix it in Spring Data JPA?
Expert LevelN+1 ProblemPerformance
+
🎯 IntentThis is a MUST-KNOW for any senior Java developer. N+1 has caused production outages at many companies. Know multiple fix strategies.
Java — N+1 Problem and all fix strategies
copy
// THE PROBLEM — Entity with lazy association: @Entity public class Order { @OneToMany(fetch = FetchType.LAZY) // default for OneToMany private List<OrderItem> items; } // This causes N+1: List<Order> orders = orderRepo.findAll(); // 1 SQL: SELECT * FROM orders for (Order o : orders) { o.getItems().size(); // N SQLs: SELECT * FROM order_items WHERE order_id=? } // → 1 + N queries total. With 1000 orders = 1001 queries! 💀 // ✅ FIX 1 — JOIN FETCH in JPQL (best for most cases) @Query("SELECT DISTINCT o FROM Order o JOIN FETCH o.items WHERE o.status = :status") List<Order> findWithItems(@Param("status") OrderStatus status); // → 1 SQL with JOIN. All data loaded at once. // ✅ FIX 2 — @EntityGraph (declarative, no JPQL) @EntityGraph(attributePaths = {"items", "items.product"}) List<Order> findByStatus(OrderStatus status); // ✅ FIX 3 — @BatchSize (loads in batches, not 1-by-1) @OneToMany @BatchSize(size = 50) private List<OrderItem> items; // → Instead of N queries: ceil(N/50) queries // ✅ FIX 4 — Projections (only select what you need) public interface OrderSummary { Long getId(); String getStatus(); int getItemCount(); // computed in JPQL } @Query("SELECT o.id as id, o.status as status, SIZE(o.items) as itemCount FROM Order o") List<OrderSummary> findAllSummaries();
⚠️ Detection: Enable SQL logging with spring.jpa.show-sql=true and logging.level.org.hibernate.SQL=DEBUG. In production, use Hibernate statistics or a tool like Datasource Proxy to detect N+1 automatically.
Q-21
What are Spring Data projections? When would you choose Interface vs DTO projection?
Senior LevelProjectionsPerformance
+
Java — Interface vs DTO Projections
copy
// Interface Projection — Spring creates proxy at runtime public interface ProductSummary { String getName(); BigDecimal getPrice(); @Value("#{target.name + ' ($' + target.price + ')'}") // SpEL expression String getDisplayName(); } List<ProductSummary> findByCategory(String category); // → SELECT name, price FROM product WHERE category=? // ↑ Only fetches needed columns — not the full entity! // DTO Projection — record or class with matching constructor public record ProductDTO(String name, BigDecimal price) {} @Query("SELECT new com.example.ProductDTO(p.name, p.price) FROM Product p WHERE p.category = :cat") List<ProductDTO> findDTOByCategory(@Param("cat") String category); // → Constructs DTO directly from JPQL — fastest approach
Interface ProjectionDTO Projection
PerformanceGood (select needed cols)Best (direct constructor)
SpEL expressions✓ Supported✗ Not supported
Derived query support✓ Works automaticallyNeeds @Query
Type safetyProxy, some limitations✓ Strong, record-based
Best forSimple column subsetsComplex computed results
09
Transactions
// @Transactional · propagation · isolation · self-invocation trap
Q-22
Explain @Transactional propagation levels with a real scenario for each.
Expert LevelPropagation
+
PropagationBehaviorReal Scenario
REQUIRED (default)Join existing tx. Create new if none exists.Most service methods — part of the main business transaction
REQUIRES_NEWAlways create new tx. Suspend existing.Audit logging — must commit even if main tx rolls back
SUPPORTSJoin existing if present. No tx if none.Read-only methods that work with or without a transaction
NOT_SUPPORTEDSuspend existing tx. Run without tx.Calling a legacy system that can't participate in transactions
MANDATORYMust have existing tx. Throw if none.Methods that should never be called outside a transaction
NEVERThrow if existing tx is present.Methods that must never run in a transactional context
NESTEDSavepoint within existing tx. Partial rollback.Processing items in a batch — rollback one item, continue others
Java — REQUIRES_NEW for audit that must always commit
copy
@Service public class OrderService { @Transactional // outer transaction public void placeOrder(Order order) { orderRepository.save(order); auditService.logAction("ORDER_PLACED", order.getId()); // separate tx if (order.getTotal().compareTo(LIMIT) > 0) { throw new BusinessException("Over limit"); // rolls back orderRepository.save() // but auditService.logAction ALREADY COMMITTED (REQUIRES_NEW) } } } @Service public class AuditService { @Transactional(propagation = Propagation.REQUIRES_NEW) public void logAction(String action, Long entityId) { auditRepo.save(new AuditLog(action, entityId)); // always commits! } }
Q-23
What is the @Transactional self-invocation trap? This is the #1 Spring interview trick question.
Expert LevelAOP ProxyCommon Trap
+
🎯 IntentThis trips up even 7-8 year developers. Knowing this shows deep Spring AOP understanding. Essential for senior roles.
Java — Self-invocation bypass + 3 fixes
copy
@Service public class OrderService { // ❌ TRAP: methodA calls methodB on 'this' — bypasses the proxy! public void methodA() { methodB(); // calls this.methodB() NOT proxy.methodB() // → @Transactional on methodB is IGNORED. No transaction! } @Transactional public void methodB() { // This @Transactional has NO EFFECT when called from methodA above } } // WHY: @Transactional works via AOP proxy. External callers call proxy.methodB() // which adds the transaction. But 'this.methodB()' bypasses the proxy entirely. // ✅ FIX 1 — Inject self (Spring Boot 2.6+ may need spring.main.allow-circular-references=true) @Service public class OrderService { @Autowired @Lazy private OrderService self; // inject the proxy reference public void methodA() { self.methodB(); // calls through proxy → @Transactional works! } } // ✅ FIX 2 — Refactor: move transactional method to a different bean // Best practice — avoid self-injection entirely // ✅ FIX 3 — Use AspectJ mode (compile-time weaving, no proxy limitation) @EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
🔴 Same rule applies to @Cacheable, @Async, @Retry, and ALL Spring AOP annotations. Any method called internally via 'this' bypasses the AOP proxy. Always design so cross-cutting concerns are invoked from outside the bean.
Q-24
Explain transaction isolation levels and their performance vs correctness tradeoffs.
Senior LevelIsolationConcurrency
+
Isolation LevelDirty ReadNon-Repeatable ReadPhantom ReadPerformance
READ_UNCOMMITTED✗ Allowed✗ Allowed✗ AllowedFastest
READ_COMMITTED (PG default)✓ Prevented✗ Allowed✗ AllowedFast
REPEATABLE_READ (MySQL default)✓ Prevented✓ Prevented✗ AllowedMedium
SERIALIZABLE✓ Prevented✓ Prevented✓ PreventedSlowest
Real advice: Never use READ_UNCOMMITTED in production. READ_COMMITTED is the safe default for most apps. Use SERIALIZABLE only for critical financial operations where phantom reads could cause double-billing. For most Java/Spring apps, rely on optimistic locking (@Version) rather than high isolation levels — it scales much better.
10
AOP & Aspects
// pointcut · advice types · real use cases · performance logging
Q-25
Write a real AOP aspect: method execution timing + automatic retry.
Expert LevelAOPReal Code
+
Java — Production AOP aspects
copy
// Custom annotation @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Timed { String value() default ""; } @Aspect @Component @Slf4j public class PerformanceAspect { // Advice 1: Around — full control of method execution @Around("@annotation(Timed)") public Object measureTime(ProceedingJoinPoint pjp) throws Throwable { long start = System.currentTimeMillis(); String method = pjp.getSignature().toShortString(); try { Object result = pjp.proceed(); // call actual method log.info("[PERF] {} completed in {}ms", method, System.currentTimeMillis() - start); return result; } catch (Throwable t) { log.error("[PERF] {} failed in {}ms", method, System.currentTimeMillis() - start); throw t; } } // Advice 2: AfterThrowing — log all service exceptions @AfterThrowing( pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex" ) public void logServiceException(JoinPoint jp, Exception ex) { log.error("Exception in {} with args {}: {}", jp.getSignature().getName(), jp.getArgs(), ex.getMessage()); } // Advice 3: Before — audit all write operations @Before("execution(* com.example.repository.*.save(..)) || execution(* com.example.repository.*.delete(..))") public void auditWrite(JoinPoint jp) { String user = SecurityContextHolder.getContext().getAuthentication().getName(); log.info("[AUDIT] User {} called {}", user, jp.getSignature()); } } // Usage — just annotate methods: @Service public class ProductService { @Timed("getProducts") public List<Product> getAllProducts() { ... } }
11
Actuator & Monitoring
// health checks · custom endpoints · Micrometer · Prometheus
Q-26
Design a production-ready Actuator configuration with custom health check.
Senior LevelActuatorMonitoring
+
application.yml — Production Actuator config
copy
management: endpoints: web: exposure: include: health,info,metrics,prometheus # expose only what's needed base-path: /management # hide from /actuator path endpoint: health: show-details: when-authorized # don't expose internals to all probes: enabled: true # /health/liveness and /health/readiness for K8s metrics: export: prometheus: enabled: true # scrape at /management/prometheus server: port: 8081 # separate port — so Nginx doesn't expose actuator externally
Java — Custom Health Indicator + Custom Metric
copy
// Custom health indicator (e.g., check external payment gateway) @Component public class PaymentGatewayHealthIndicator implements HealthIndicator { @Override public Health health() { try { boolean isUp = paymentClient.ping(); return isUp ? Health.up().withDetail("gateway", "Stripe reachable").build() : Health.down().withDetail("gateway", "Stripe unreachable").build(); } catch (Exception e) { return Health.down(e).build(); } } } // Custom business metric (Micrometer) @Service public class OrderService { private final Counter ordersPlaced; private final Timer orderProcessingTime; public OrderService(MeterRegistry registry) { ordersPlaced = Counter.builder("orders.placed.total") .description("Total orders placed") .tag("region", "india") .register(registry); orderProcessingTime = Timer.builder("orders.processing.time") .register(registry); } public Order placeOrder(OrderRequest req) { return orderProcessingTime.record(() -> { Order order = doProcess(req); ordersPlaced.increment(); return order; }); } }
12
Testing
// @SpringBootTest · @WebMvcTest · @DataJpaTest · Testcontainers
Q-27
What's the difference between @SpringBootTest, @WebMvcTest, and @DataJpaTest? When to use each?
Senior LevelTesting Slices
+
AnnotationLoadsSpeedUse When
@SpringBootTestFull application contextSlowest (~3-10s)Integration tests — test full flow end-to-end
@WebMvcTestOnly web layer (controllers, filters, advice)Fast (~1s)Unit testing controllers in isolation
@DataJpaTestOnly JPA repositories + in-memory DBMediumTest repository queries — custom JPQL, derived queries
@JsonTestJackson + JSON serialization onlyFastestTest JSON serialization/deserialization
@RestClientTestRestTemplate + MockServerFastTest REST clients in isolation
Java — @WebMvcTest + @DataJpaTest + Testcontainers
copy
// 1. @WebMvcTest — controller slice test @WebMvcTest(OrderController.class) class OrderControllerTest { @Autowired MockMvc mockMvc; @MockBean OrderService orderService; // @Service not loaded, must mock @Test void shouldReturn201WhenOrderCreated() throws Exception { given(orderService.create(any())).willReturn(new OrderResponse(1L, "PLACED")); mockMvc.perform(post("/api/orders") .contentType(APPLICATION_JSON) .content("""{"customerId":"C1","items":[{"productId":"P1","qty":2}]}""")) .andExpect(status().isCreated()) .andExpect(jsonPath("$.status").value("PLACED")); } } // 2. @DataJpaTest with Testcontainers (real PostgreSQL!) @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Testcontainers class OrderRepositoryTest { @Container static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine"); @Autowired OrderRepository orderRepository; @Test void shouldFindOrdersByCustomer() { // uses REAL PostgreSQL container — no H2 quirks! Order order = orderRepository.save(new Order("C1", OrderStatus.PLACED)); assertThat(orderRepository.findByCustomerId("C1")).hasSize(1); } }
13
Microservices with Spring Boot
// Spring Cloud · Feign · Circuit Breaker · Kafka · service discovery
Q-28
Implement a Circuit Breaker pattern using Resilience4j in Spring Boot.
Expert LevelCircuit BreakerResilience
+
Java + YAML — Circuit Breaker with Feign Client
copy
# application.yml resilience4j: circuitbreaker: instances: paymentService: slidingWindowSize: 10 failureRateThreshold: 50 # open after 50% failures waitDurationInOpenState: 5s # wait before HALF_OPEN permittedCallsInHalfOpenState: 3 retry: instances: paymentService: maxAttempts: 3 waitDuration: 500ms retryExceptions: [java.io.IOException, java.net.ConnectException] // Java — annotate your service method @Service public class OrderService { @CircuitBreaker(name = "paymentService", fallbackMethod = "paymentFallback") @Retry(name = "paymentService") public PaymentResponse processPayment(PaymentRequest req) { return paymentClient.charge(req); // calls external service } // Fallback — same return type + Throwable parameter public PaymentResponse paymentFallback(PaymentRequest req, Throwable t) { log.error("Payment service unavailable: {}", t.getMessage()); return PaymentResponse.pending(req.getOrderId()); // graceful degradation } }
🌱 States to explain: CLOSED (normal) → OPEN (failing, calls go to fallback immediately) → HALF_OPEN (testing recovery with limited calls) → CLOSED again if recovery succeeds. This prevents cascading failures across microservices.
Q-29
How do you implement Kafka producer and consumer in Spring Boot with error handling?
Expert LevelKafkaMessaging
+
Java — Kafka Producer + Consumer + DLQ
copy
// Producer @Service @RequiredArgsConstructor public class OrderEventPublisher { private final KafkaTemplate<String, OrderEvent> kafkaTemplate; public void publishOrderPlaced(Order order) { OrderEvent event = new OrderEvent(order.getId(), "PLACED", Instant.now()); kafkaTemplate.send("order-events", order.getId().toString(), event) .whenComplete((result, ex) -> { if (ex != null) { log.error("Failed to publish order event for {}", order.getId(), ex); // store in outbox table for retry (Outbox pattern) } else { log.info("Published to partition {}", result.getRecordMetadata().partition()); } }); } } // Consumer with error handling + DLQ @Component @Slf4j public class OrderEventConsumer { @KafkaListener( topics = "order-events", groupId = "inventory-service", containerFactory = "orderEventFactory" ) public void handleOrderEvent(OrderEvent event, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic, Acknowledgment ack) { try { // check idempotency first if (!processedEvents.contains(event.getId())) { inventoryService.reserve(event.getOrderId()); processedEvents.add(event.getId()); } ack.acknowledge(); // manual ack — at-least-once } catch (Exception e) { log.error("Error processing event {}: {}", event.getId(), e.getMessage()); // Don't ack → message goes back to retry topic via error handler throw e; } } } // Dead Letter Topic config @Bean public DefaultErrorHandler errorHandler(KafkaTemplate<String, Object> template) { var deadLetterPublisher = new DeadLetterPublishingRecoverer(template, (rec, ex) -> new TopicPartition(rec.topic() + ".DLT", rec.partition())); BackOff backOff = new FixedBackOff(1000L, 3); // 3 retries, 1s apart return new DefaultErrorHandler(deadLetterPublisher, backOff); }
14
Performance & Tuning
// startup time · caching · connection pooling · lazy init
Q-30
How do you optimize Spring Boot startup time and memory footprint?
Senior LevelPerformanceProduction
+
application.yml + Java — Startup Optimization
copy
# 1. Lazy initialization — beans created on first use, not at startup spring: main: lazy-initialization: true # 2. Narrow component scan scope # In @SpringBootApplication: scanBasePackages="com.example.mymodule" # 3. Exclude unused auto-configurations spring: autoconfigure: exclude: - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration // 4. Connection pool tuning (HikariCP — default in Spring Boot) spring: datasource: hikari: maximum-pool-size: 20 # max connections minimum-idle: 5 # always keep 5 ready connection-timeout: 30000 # 30s max wait for connection idle-timeout: 600000 # 10min idle before release // 5. Spring Boot 3 AOT (Ahead-of-Time compilation) // mvn -Pnative native:compile → builds native binary with sub-second startup // 6. @Cacheable for expensive operations @Service public class ProductService { @Cacheable(value = "products", key = "#id", unless = "#result == null") public Product findById(Long id) { return productRepository.findById(id).orElse(null); } @CacheEvict(value = "products", key = "#product.id") public Product update(Product product) { return productRepository.save(product); } @CacheEvict(value = "products", allEntries = true) public void clearCache() {} }
SC
Scenario-Based Programming Questions
// real problems interviewers ask senior candidates to code
S-01
Scenario: Your service must call 3 external APIs in parallel and combine results. One API failing should not fail the whole request. How do you implement this?
Expert LevelCompletableFutureAsync
+
📦 Real Scenario — Product Detail Page (like Flipkart)
Java — Parallel API calls with CompletableFuture
copy
@Service @RequiredArgsConstructor public class ProductAggregatorService { private final ProductClient productClient; private final InventoryClient inventoryClient; private final ReviewClient reviewClient; private final Executor asyncExecutor; // configured thread pool public ProductDetailResponse getProductDetails(String productId) { // Fire all 3 API calls in parallel CompletableFuture<ProductInfo> productFuture = CompletableFuture.supplyAsync(() -> productClient.getProduct(productId), asyncExecutor) .exceptionally(ex -> { log.error("Product API failed", ex); return null; // degrade gracefully }); CompletableFuture<InventoryInfo> inventoryFuture = CompletableFuture.supplyAsync(() -> inventoryClient.getStock(productId), asyncExecutor) .exceptionally(ex -> { log.warn("Inventory API failed — showing unknown stock", ex); return new InventoryInfo("UNKNOWN"); }); CompletableFuture<ReviewSummary> reviewFuture = CompletableFuture.supplyAsync(() -> reviewClient.getSummary(productId), asyncExecutor) .exceptionally(ex -> { log.warn("Reviews API failed — showing no reviews", ex); return ReviewSummary.empty(); }); // Wait for ALL to complete (even if some failed) CompletableFuture.allOf(productFuture, inventoryFuture, reviewFuture) .orTimeout(2, TimeUnit.SECONDS) // fail fast after 2 seconds total .join(); return ProductDetailResponse.builder() .product(productFuture.join()) .inventory(inventoryFuture.join()) .reviews(reviewFuture.join()) .build(); } } // Configure dedicated thread pool for async calls @Bean("asyncExecutor") public Executor asyncExecutor() { return new ThreadPoolExecutor( 10, 50, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100), new ThreadPoolExecutor.CallerRunsPolicy() ); }
S-02
Scenario: You need to process 1 million records from a DB without OOM. Design a streaming batch solution.
Expert LevelStreamingMemory
+
📦 Real Scenario — Monthly invoice generation for 1M customers
Java — Stream-based processing to avoid OOM
copy
// ❌ WRONG — loads all 1M records into heap → OOM List<Order> orders = orderRepository.findAll(); // 💀 // ✅ FIX 1 — JPA Stream (processes one at a time, within transaction) @Repository public interface OrderRepository extends JpaRepository<Order, Long> { @Query("SELECT o FROM Order o WHERE o.status = 'PENDING'") @QueryHints(@QueryHint(name = HINT_FETCH_SIZE, value = "50")) Stream<Order> streamAllPendingOrders(); } @Service @Transactional(readOnly = true) public class InvoiceGeneratorService { public void generateMonthlyInvoices() { try (Stream<Order> stream = orderRepository.streamAllPendingOrders()) { stream .filter(o -> o.isEligibleForInvoice()) .map(this::createInvoice) .forEach(invoice -> { invoiceService.save(invoice); emailService.sendAsync(invoice); EntityManagerHolder.clear(); // flush L1 cache periodically }); } } } // ✅ FIX 2 — Spring Batch with chunked processing @Bean public Step generateInvoicesStep() { return stepBuilderFactory .<Order, Invoice>get("generateInvoices") .chunk(100) // read 100, process 100, write 100, commit → repeat .reader(orderItemReader()) .processor(orderToInvoiceProcessor()) .writer(invoiceWriter()) .faultTolerant() .retry(TransientDataAccessException.class).retryLimit(3) .skip(IllegalArgumentException.class).skipLimit(10) .build(); }
S-03
Scenario: Design a distributed rate limiter using Spring Boot + Redis that works across multiple instances.
Expert LevelRate LimitingRedisDistributed
+
Java — Distributed Rate Limiter with Redis + AOP
copy
// Custom annotation @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RateLimit { int requests() default 100; int seconds() default 60; String key() default "default"; } // AOP Aspect for rate limiting @Aspect @Component @RequiredArgsConstructor public class RateLimitAspect { private final RedisTemplate<String, String> redisTemplate; @Around("@annotation(rateLimit)") public Object rateLimit(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable { String userId = SecurityContextHolder.getContext().getAuthentication().getName(); String key = "rate:" + rateLimit.key() + ":" + userId; // Atomic increment + expire using Lua script (no race condition) Long count = redisTemplate.execute(new DefaultRedisScript<>( """ local count = redis.call('INCR', KEYS[1]) if count == 1 then redis.call('EXPIRE', KEYS[1], ARGV[1]) end return count """, Long.class ), List.of(key), String.valueOf(rateLimit.seconds())); if (count > rateLimit.requests()) { throw new RateLimitExceededException( "Rate limit exceeded. Max " + rateLimit.requests() + " requests per " + rateLimit.seconds() + "s"); } return pjp.proceed(); } } // Usage @RestController public class SearchController { @GetMapping("/search") @RateLimit(requests = 20, seconds = 60, key = "search") public ResponseEntity<SearchResult> search(@RequestParam String q) { ... } }
S-04
Scenario: Implement the Outbox Pattern to guarantee reliable event publishing without 2-phase commit.
Expert LevelOutbox PatternEvent-Driven
+
📦 Problem: saveOrder() + publishEvent() — if Kafka is down after DB save, event is lost forever
Java — Outbox Pattern implementation
copy
// Step 1: Outbox table entity — persisted in SAME transaction as business data @Entity public class OutboxEvent { @Id @GeneratedValue private UUID id; private String aggregateType; // "ORDER" private String eventType; // "ORDER_PLACED" private String payload; // JSON private OutboxStatus status = PENDING; private Instant createdAt; } // Step 2: Save order + outbox event atomically @Service @Transactional public class OrderService { public Order placeOrder(OrderRequest req) { Order order = orderRepository.save(createOrder(req)); // Write event to outbox IN SAME transaction — atomicity guaranteed outboxRepository.save(new OutboxEvent( "ORDER", "ORDER_PLACED", toJson(order) )); // NO Kafka call here — Kafka is eventually consistent return order; } } // Step 3: Outbox poller — separate scheduled job publishes to Kafka @Component @Slf4j @RequiredArgsConstructor public class OutboxPoller { private final OutboxRepository outboxRepo; private final KafkaTemplate<String, String> kafka; @Scheduled(fixedDelay = 1000) // every second @Transactional public void poll() { List<OutboxEvent> pending = outboxRepo.findTop100ByStatusOrderByCreatedAtAsc(PENDING); for (OutboxEvent event : pending) { try { kafka.send(event.getAggregateType().toLowerCase() + "-events", event.getAggregateId().toString(), event.getPayload()).get(5, TimeUnit.SECONDS); event.setStatus(PUBLISHED); } catch (Exception e) { event.incrementRetry(); if (event.getRetries() > 5) event.setStatus(FAILED); } } } }
Why Outbox Pattern works: Order + OutboxEvent save together in one ACID transaction. If Kafka is down, the row stays in PENDING state. The poller retries indefinitely. Exactly-once semantics achieved by combining the outbox (at-least-once) with idempotent consumers.
S-05
Scenario: A specific endpoint is very slow. Walk me through your performance debugging and fix approach.
Senior LevelDebuggingPerformance
+
Step 1 — Measure first
Add Micrometer timer. Check P50/P95/P99. Is it consistent or intermittent? Intermittent → likely DB connection pool exhaustion or GC.
Step 2 — Check SQL
Enable spring.jpa.show-sql=true. Use Datasource Proxy or Hibernate Statistics. Find N+1 queries, missing indexes, full table scans.
Step 3 — Check blocking calls
Is there a synchronous external API call in the hot path? Move to async or cache the result.
Step 4 — Check connection pool
Monitor Hikari metrics: hikaricp.connections.pending. Pool exhaustion looks like random slowness.
Common Fixes
Add DB index, fix N+1 with @EntityGraph, cache with @Cacheable + Redis, pagination instead of full load, async processing with @Async or Kafka.
S-06
Scenario: Implement an @Async method with a custom thread pool and proper exception handling.
Senior Level@AsyncThread Pool
+
Java — @Async with custom executor + exception handler
copy
@Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(20); executor.setQueueCapacity(500); executor.setThreadNamePrefix("Async-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return (ex, method, params) -> { log.error("Async error in {}: {}", method.getName(), ex.getMessage(), ex); // alert, metrics, etc. }; } } @Service public class NotificationService { // For fire-and-forget: @Async public void sendEmail(EmailRequest req) { emailClient.send(req); // runs in async thread pool } // For async with result (return Future to track completion): @Async public CompletableFuture<String> sendPushNotification(PushRequest req) { String result = pushClient.send(req); return CompletableFuture.completedFuture(result); } } // ⚠️ SAME SELF-INVOCATION TRAP applies to @Async! // Calling this.sendEmail() from within NotificationService bypasses @Async
Complete Spring Boot Annotations Cheat Sheet
// every annotation you must know — grouped by category

Core / Bean Configuration

@SpringBootApplication
Entry point. Combines @Configuration, @EnableAutoConfiguration, @ComponentScan.
@Configuration
Marks class as source of @Bean definitions. Processed by CGLIB proxy.
@Bean
Method-level. Return value registered as Spring bean. Used inside @Configuration.
@Component
Generic bean registration via component scan.
@Service
Business logic layer stereotype. Same as @Component functionally.
@Repository
DAO layer. Enables exception translation from persistence exceptions.
@Scope("prototype")
Controls bean scope: singleton, prototype, request, session.
@Lazy
Defers bean creation until first use. Useful for heavy-init beans.
@PostConstruct
Method called after injection complete. Initialize resources here.
@PreDestroy
Called before bean destroyed. Close connections, flush buffers.
@Primary
Marks preferred bean when multiple implementations exist.
@Qualifier
Explicit bean selection by name. Overrides @Primary.

Web / REST

@RestController
= @Controller + @ResponseBody. All methods return serialized response body.
@RequestMapping
Maps URLs to class/methods. Base path at class level, specific at method level.
@GetMapping / @PostMapping
Shorthand for @RequestMapping(method=GET/POST). Always prefer over @RequestMapping.
@PathVariable
Extracts value from URI path: /users/{id} → @PathVariable Long id
@RequestParam
Query parameter: ?page=1 → @RequestParam(defaultValue="1") int page
@RequestBody
Deserializes JSON request body to Java object. Uses HttpMessageConverter (Jackson).
@ResponseStatus
Sets HTTP status code for a method or exception handler class.
@RestControllerAdvice
Global exception handler for all REST controllers. Combines @ControllerAdvice + @ResponseBody.
@ExceptionHandler
Handles specific exception types within @ControllerAdvice.
@CrossOrigin
Enables CORS on specific endpoint. Prefer global config in SecurityFilterChain.

Data / JPA

@Entity
Marks class as JPA entity mapped to a DB table.
@Table(name="orders")
Specifies table name, schema, unique constraints.
@Id / @GeneratedValue
Primary key + generation strategy (IDENTITY, SEQUENCE, AUTO).
@Column
Column mapping: name, nullable, unique, length, columnDefinition.
@OneToMany / @ManyToOne
Relationship mappings. Always specify fetch type explicitly.
@JoinColumn
Specifies FK column in the owning side of a relationship.
@Version
Optimistic locking. Hibernate adds version check on UPDATE. Prevents lost updates.
@Query
Custom JPQL or native SQL. nativeQuery=true for raw SQL.
@Modifying @Transactional
Required for @Query that does UPDATE/DELETE. Always combine both.
@EntityGraph
Eager load specific associations per query. Fix for N+1.

Config / Conditional / Security

@Value("${prop}")
Inject single property. Supports SpEL: @Value("#{2 * 3}").
@ConfigurationProperties
Binds entire config prefix to POJO. Type-safe, validatable.
@Profile
Bean only loaded for matching active profile(s).
@ConditionalOnMissingBean
Creates bean only if no bean of that type exists. Most-used conditional.
@ConditionalOnProperty
Creates bean only if config property has specified value.
@EnableWebSecurity
Activates Spring Security. Required in Spring Boot 3 security config.
@PreAuthorize
Method-level security with SpEL. Requires @EnableMethodSecurity.
@Transactional
Wraps method in DB transaction. AOP-based — beware self-invocation trap!
@Cacheable / @CacheEvict
Method result caching. Also beware of self-invocation!
@Async
Runs method in separate thread pool. Returns void or CompletableFuture.
@Scheduled
Cron or fixed-rate scheduling. Requires @EnableScheduling.
@EventListener
Listen to ApplicationEvents. Decoupled communication within service.

Critical Rules — Self-invocation Trap applies to ALL of these

🔴 The Universal AOP Proxy Rule: These annotations ALL use Spring AOP proxies. Calling them via this.method() internally bypasses the proxy. The annotation has NO effect:
@Transactional   @Cacheable   @Async   @Retryable   @RateLimiter   @CircuitBreaker

Always invoke these methods from outside the class (via another bean) to ensure the proxy intercepts the call.