0% found this document useful (0 votes)
35 views55 pages

Spring Boot Interview Guide

The document is a comprehensive interview guide for Spring Boot, covering key topics such as Spring Framework concepts, request internal flow, annotations, and dependency injection. It provides detailed explanations of various components, their lifecycle, and best practices, along with code examples. The guide serves as a reference for understanding Spring Boot's features and functionalities, particularly for Java 17 and Spring Boot 3.x.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
35 views55 pages

Spring Boot Interview Guide

The document is a comprehensive interview guide for Spring Boot, covering key topics such as Spring Framework concepts, request internal flow, annotations, and dependency injection. It provides detailed explanations of various components, their lifecycle, and best practices, along with code examples. The guide serves as a reference for understanding Spring Boot's features and functionalities, particularly for Java 17 and Spring Boot 3.x.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Spring Boot Interview Guide

Java 17 • Spring Boot 3.x • Detailed Reference with Code Examples

📋 Index
# Section Topics Covered
1 Spring & Spring Boot IoC, auto-configuration, starters, ApplicationContext,
@SpringBootApplication
2 Request Internal Flow DispatcherServlet lifecycle, Filter vs Interceptor vs AOP,
@RequestBody
3 Annotations & Tricky Stereotype annotations, @Autowired types, @Configuration
Questions CGLIB, @Conditional, @Value
4 Bean Lifecycle & Full lifecycle order, all scopes, BeanPostProcessor, prototype-
Scopes in-singleton
5 Dependency Injection Circular dependencies, 3-level cache, @Lazy, @Primary vs
@Qualifier
6 JPA & Hibernate Entity states, N+1 problem, L1/L2 cache, fetch types,
GeneratedValue, save vs persist
7 Transactions All 7 propagation types, isolation levels, rollback rules,
TransactionTemplate
8 Spring Security Filter chain, Auth vs Authz, JWT flow, @PreAuthorize, CSRF
9 Application Properties Property precedence, profiles, @ConfigurationProperties, key
& Config properties
10 Actuator & Monitoring Key endpoints, custom HealthIndicator, Micrometer
11 AOP Core concepts, JDK proxy vs CGLIB, @Around advice
12 Async & Scheduling @Async internals, thread pool config, @Scheduled modes
13 Testing @SpringBootTest vs slice tests, @MockBean vs @Mock,
@SpyBean
14 Misc & Tricky @ControllerAdvice, Spring events, caching annotations,
CommandLineRunner, @Retryable

1. Spring & Spring Boot


Core concepts, differences, and boot internals
What is Spring Framework and what problems does it solve? Easy

Before Spring, Java EE required heavy XML configuration, tight coupling, and container-managed
EJBs. Spring introduced a lightweight alternative through two core principles:
IoC (Inversion of Control) — The container controls object creation and lifecycle. You don't call
new MyService() ; Spring creates and injects it.

DI (Dependency Injection) — Dependencies are "pushed in" by the container rather than the
object fetching them.
WITHOUT SPRING (TIGHT COUPLING)
public class OrderService {
private PaymentService paymentService = new PaymentService(); // hard-coded, un
private EmailService emailService = new EmailService();
}

WITH SPRING (LOOSE COUPLING)


@Service
public class OrderService {
private final PaymentService paymentService; // injected by Spring
private final EmailService emailService;

public OrderService(PaymentService paymentService, EmailService emailService) {


[Link] = paymentService;
[Link] = emailService;
}
}

Other pillars: AOP (cross-cutting concerns like logging, transactions), Spring MVC (web layer),
Spring Data (data access abstraction), Spring Security (auth/authz).
Spring vs Spring Boot — key differences? Med

Spring Spring Boot


Manual XML/Java configuration Auto-configuration via
@EnableAutoConfiguration

No embedded server — deploy WAR to Embedded Tomcat/Jetty/Undertow — run as JAR


Tomcat
Manage dependency versions manually Starter POMs + BOM manage all versions
No production features built-in Actuator, health checks, metrics out-of-the-box
Hundreds of lines of boilerplate config Convention over configuration — sensible defaults
SPRING BOOT MAIN CLASS — MINIMAL SETUP

@SpringBootApplication // replaces 100+ lines of XML config


public class Application {
public static void main(String[] args) {
[Link]([Link], args);
}
}

Spring Boot does NOT replace Spring — it's built on top of Spring and simplifies its setup.
How does Spring Boot Auto-Configuration work internally? Hard

@SpringBootApplication → @EnableAutoConfiguration → AutoConfigurationImportSelector


→ Reads [Link] → Each class evaluated via @Conditional →
Matching beans registered
Spring Boot 3.x reads from META-
INF/spring/[Link] (replaces
old [Link] ). Each listed class is a @Configuration class guarded by conditions.
EXAMPLE: DATASOURCEAUTOCONFIGURATION (SIMPLIFIED)
@AutoConfiguration
@ConditionalOnClass({ [Link], [Link] })
@ConditionalOnMissingBean(type = "[Link]")
@EnableConfigurationProperties([Link])
public class DataSourceAutoConfiguration {

@Bean
@ConditionalOnMissingBean // only if YOU haven't defined a DataSource bean
public DataSource dataSource(DataSourceProperties props) {
return [Link]().build();
}
}

@ConditionalOnClass — only registers if DataSource is on classpath


@ConditionalOnMissingBean — your custom DataSource bean overrides this

💡 Run app with --debug or set


[Link]=DEBUG to see CONDITIONS
EVALUATION REPORT — shows every auto-config that matched or was skipped and why.
What does @SpringBootApplication contain? Tricky

It is a composed meta-annotation — shortcut for three annotations:


@Target([Link])
@Retention([Link])
@Documented
@Inherited
@SpringBootConfiguration // ← @Configuration
@EnableAutoConfiguration // ← triggers auto-config
@ComponentScan // ← scans current package + sub-packages
public @interface SpringBootApplication { ... }

CUSTOMISING SCAN BASE PACKAGES


// Option 1: attribute
@SpringBootApplication(scanBasePackages = { "[Link]", "[Link]" }

// Option 2: exclude an auto-config


@SpringBootApplication(exclude = { [Link] })

⚠️ If main class is in the default package (no package declaration), @ComponentScan scans all
classes in the entire classpath — causing performance issues and unexpected bean registration.
Always use a named package like [Link] .
BeanFactory vs ApplicationContext — differences? Med

Feature BeanFactory ApplicationContext


Bean instantiation Lazy (on first getBean()) Eager (all singletons at startup)
AOP support No Yes
Event publishing No Yes
(ApplicationEventPublisher)
Internationalization No Yes (MessageSource)
Environment No Yes
abstraction
Use case Resource-constrained All standard Spring apps
environments
Spring Boot uses AnnotationConfigServletWebServerApplicationContext for servlet web apps
and AnnotationConfigReactiveWebServerApplicationContext for reactive apps.
// Getting beans programmatically
@Autowired
ApplicationContext ctx;

MyService svc = [Link]([Link]);


String[] names = [Link](); // all registered beans

2. Request Internal Flow


How an HTTP request travels through Spring MVC
Walk through a Spring MVC request lifecycle in detail Hard

1. HTTP Request → 2. Servlet Filters → 3. DispatcherServlet → 4. HandlerMapping →


5. Interceptor preHandle → 6. HandlerAdapter + ArgumentResolvers → 7. Controller Method
→ 8. ReturnValueHandler / MessageConverter → 9. Interceptor postHandle →
10. afterCompletion → 11. HTTP Response
STEP-BY-STEP DETAIL
Filters (step 2) — Servlet-level, run before Spring. Used for CORS, auth token extraction, request
wrapping. Registered as @Component implementing Filter or via
FilterRegistrationBean .

DispatcherServlet (step 3) — Front Controller. Auto-registered by Spring Boot mapped to / .


Delegates everything.
HandlerMapping (step 4) — RequestMappingHandlerMapping scans all @RequestMapping
methods and picks the best match based on URL, HTTP method, headers, params.
Interceptor preHandle (step 5) — Returns true to continue or false to abort. Can set
response directly.
HandlerAdapter (step 6) — RequestMappingHandlerAdapter resolves method arguments
( @RequestBody , @PathVariable , @RequestParam , Principal , etc.) using
HandlerMethodArgumentResolver implementations.

MessageConverter (step 8) — For REST, MappingJackson2HttpMessageConverter serialises


return object to JSON.
afterCompletion (step 10) — Always runs even if exception occurred. Good for resource cleanup.
CUSTOM INTERCEPTOR EXAMPLE
@Component
public class RequestLoggingInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Objec
[Link]("Incoming: {} {}", [Link](), [Link]());
[Link]("startTime", [Link]());
return true; // continue
}

@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res,
Object handler, Exception ex) {
long duration = [Link]() - (long) [Link]("start
[Link]("Completed in {}ms, status={}", duration, [Link]());
}
}

@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired RequestLoggingInterceptor interceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
[Link](interceptor).addPathPatterns("/api/**");
}
}
Filter vs Interceptor vs AOP Aspect — detailed comparison Tricky

Feature Filter Interceptor AOP Aspect


Scope Servlet container level Spring MVC level Any Spring bean
method
Spring beans No (unless Yes Yes
accessible DelegatingFilterProxy)
Access to No Yes Via JoinPoint
handler info ( HandlerMethod )
Applies to static Yes No No
resources
Granularity Request/Response Controller methods Any method on any
bean
Use case CORS, encoding, security Auth check, locale, Transaction,
token extraction logging caching, audit
logging
FILTER — CORS EXAMPLE
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
[Link]("Access-Control-Allow-Origin", "*");
[Link](req, res); // must call this or request is blocked
}
}
How does @RequestBody deserialization work internally? Med

1. DispatcherServlet passes to RequestMappingHandlerAdapter


2. It finds RequestResponseBodyMethodProcessor as the argument resolver for @RequestBody
3. Processor iterates registered HttpMessageConverter s — picks one whose canRead() returns
true based on Content-Type header
4. MappingJackson2HttpMessageConverter uses Jackson ObjectMapper to deserialize JSON
stream into the target type
5. If @Valid or @Validated present, MethodValidationInterceptor triggers Bean Validation
(JSR-380)
@PostMapping("/orders")
public ResponseEntity<OrderResponse> createOrder(
@RequestBody @Valid CreateOrderRequest request) { // ← deserialized + valid
return [Link]([Link]).body([Link](reque
}

public record CreateOrderRequest(


@NotBlank String productId,
@Min(1) int quantity,
@NotNull BigDecimal price
) {}

⚠️ @RequestBody reads the servlet InputStream — it can only be read once. If you need to read it
in a Filter too, wrap the request: new ContentCachingRequestWrapper(request)

3. Annotations & Tricky Questions


Internals and gotchas for the most commonly asked annotations
@Component vs @Service vs @Repository vs @Controller Tricky

All four are stereotype annotations — all trigger component scanning. They are specializations of
@Component with added semantics:

Annotation Extra Behaviour Layer


@Component None — generic bean Any
@Repository Enables persistence exception translation — wraps DB- Data
specific exceptions (SQLException, JPA exceptions) into access
Spring's DataAccessException hierarchy
@Service None currently — reserved for future use by Spring. Acts as Business
documentation. logic
@Controller Detected by DispatcherServlet as web handler. Web
Methods can return view names. (MVC)
@RestController @Controller + @ResponseBody on all methods. Web
Return values are serialized directly to HTTP response body. (REST)
EXCEPTION TRANSLATION IN ACTION (@REPOSITORY)
@Repository
public class UserRepository {
public User findById(long id) {
// If a DB-specific exception (e.g. PSQLException) is thrown here,
// Spring translates it to DataAccessException automatically.
// Without @Repository, raw vendor-specific exception propagates.
}
}
@Autowired — field vs constructor vs setter injection Tricky

FIELD INJECTION (AVOID)


@Service
public class OrderService {
@Autowired
private PaymentService paymentService; // can't be final, can't test without Sp
}

SETTER INJECTION (OPTIONAL DEPENDENCIES)


@Service
public class OrderService {
private PaymentService paymentService;

@Autowired(required = false) // optional dependency


public void setPaymentService(PaymentService paymentService) {
[Link] = paymentService;
}
}

CONSTRUCTOR INJECTION ✅ RECOMMENDED


@Service
@RequiredArgsConstructor // Lombok — generates constructor for all final fields
public class OrderService {
private final PaymentService paymentService; // immutable, clearly required
private final EmailService emailService;
// Spring 4.3+: @Autowired not needed if single constructor
}

Why constructor injection wins: fields can be final (immutable), dependencies are explicit, easy to
unit test without Spring container, no NullPointerException surprises.
Multiple beans of same type — how to resolve ambiguity? Tricky

public interface NotificationService { void send(String msg); }

@Service public class EmailNotificationService implements NotificationService { ..


@Service public class SmsNotificationService implements NotificationService { ..
@Service public class PushNotificationService implements NotificationService { ..

OPTION 1: @PRIMARY — DEFAULT BEAN


@Primary @Service
public class EmailNotificationService implements NotificationService { ... }

OPTION 2: @QUALIFIER — EXPLICIT SELECTION


@Autowired
@Qualifier("smsNotificationService")
private NotificationService notificationService;

OPTION 3: INJECT ALL IMPLEMENTATIONS


@Autowired
private List<NotificationService> allNotifiers; // [email, sms, push]

@Autowired
private Map<String, NotificationService> notifierMap;
// {"emailNotificationService": ..., "smsNotificationService": ..., ...}

// Usage: send via all channels


public void broadcast(String msg) {
[Link](n -> [Link](msg));
}
@Configuration vs @Component — CGLIB proxy difference Hard

@Configuration classes are subclassed by CGLIB at runtime. Every @Bean method call is
intercepted and returns the existing singleton rather than creating a new object.
@CONFIGURATION — SINGLETON GUARANTEED
@Configuration
public class AppConfig {

@Bean
public ServiceA serviceA() {
return new ServiceA(sharedRepo()); // sharedRepo() intercepted → returns si
}

@Bean
public ServiceB serviceB() {
return new ServiceB(sharedRepo()); // same instance as above!
}

@Bean
public SharedRepo sharedRepo() { return new SharedRepo(); }
}

@COMPONENT (LITE MODE) — CREATES NEW INSTANCE EACH CALL!

@Component // NOT proxied by CGLIB


public class AppConfig {
@Bean public ServiceA serviceA() { return new ServiceA(sharedRepo()); }
@Bean public ServiceB serviceB() { return new ServiceB(sharedRepo()); }
// BUG: serviceA and serviceB each get a DIFFERENT SharedRepo instance!
@Bean public SharedRepo sharedRepo() { return new SharedRepo(); }
}

💡 Use @Configuration(proxyBeanMethods = false) when @Bean methods never call each


other — avoids CGLIB overhead and is faster at startup (Spring Boot internal configs use this).
@Transactional on private method — does it work? Self-invocation trap? Tricky

Spring AOP works by wrapping your bean in a proxy. The proxy intercepts method calls. Two cases
where @Transactional is silently ignored:
CASE 1: PRIVATE METHOD — PROXY CAN'T SEE IT
@Service
public class UserService {
@Transactional // ❌ IGNORED — CGLIB cannot override private methods
private void saveUserInternal(User user) { ... }
}

CASE 2: SELF-INVOCATION — BYPASSES PROXY


@Service
public class UserService {

public void createUser(User user) {


saveUserInternal(user); // ❌ direct call on 'this', NOT via proxy → no tra
}

@Transactional
public void saveUserInternal(User user) { ... }
}

FIX: INJECT SELF OR EXTRACT TO ANOTHER BEAN


@Service
public class UserService {
@Autowired
private UserService self; // inject proxy of itself

public void createUser(User user) {


[Link](user); // ✅ goes through proxy → transaction starts
}

@Transactional
public void saveUserInternal(User user) { ... }
}
⚠️ Same proxy limitation applies to @Async , @Cacheable , @Retryable , and all AOP-based
annotations.
@Value vs @ConfigurationProperties — when to use which? Med

@VALUE — SINGLE PROPERTY, SUPPORTS SPEL


@Service
public class PaymentService {
@Value("${[Link]}")
private String gatewayUrl;

@Value("${[Link]}") // default value 5000 if not set


private int timeoutMs;

@Value("#{systemProperties['[Link]']}") // SpEL expression


private String javaHome;
}

@CONFIGURATIONPROPERTIES — GROUPED, TYPE-SAFE, VALIDATED


# [Link]
payment:
gateway:
url: [Link]
timeout: 5000
retry-count: 3
supported-currencies:
- USD
- EUR

@ConfigurationProperties(prefix = "[Link]")
@Validated
public class PaymentGatewayProperties {

@NotBlank
private String url;

@Min(1000) @Max(30000)
private int timeout;

private int retryCount = 3; // default value

private List<String> supportedCurrencies;


// getters/setters or use record
}

@PostConstruct vs InitializingBean vs @Bean(initMethod) — order and use Med

@Component
public class CacheManager implements InitializingBean {

@Autowired
private DataSource dataSource; // step 1: injected

@PostConstruct
public void init() {
// step 2: @PostConstruct runs first — good for validation/logging
[Link]("DataSource injected: {}", dataSource != null);
warmUpCache();
}

@Override
public void afterPropertiesSet() {
// step 3: InitializingBean — runs after @PostConstruct
validateConfiguration();
}

@PreDestroy
public void cleanup() {
// called on context shutdown — clear cache, close connections
[Link]();
}
}

Execution order: Constructor → @Autowired field injection → @PostConstruct →


afterPropertiesSet() → @Bean(initMethod)

✅ Prefer @PostConstruct — it's JSR-250 standard (framework-agnostic), no Spring API


coupling. Use @Bean(initMethod) when you can't modify the class (third-party libraries).

4. Bean Lifecycle & Scopes


How Spring creates, initialises, and destroys beans
Explain the complete Spring Bean Lifecycle Hard

1. Instantiation (Constructor) → 2. Populate Properties (@Autowired) →


3. [Link]() → 4. [Link]() →
5. [Link]() → 6. [Link]() →
7. @PostConstruct → 8. [Link]() → 9. @Bean(initMethod) →
10. [Link]() ← AOP proxy created here → 11. Bean Ready →
12. @PreDestroy → 13. [Link]()
OBSERVING LIFECYCLE WITH A DEMO BEAN
@Component
public class LifecycleDemo implements
BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean,

public LifecycleDemo() { [Link]("1. Constructor called"); }

@Autowired
public void setDep(SomeDep dep) { [Link]("2. @Autowired set"); }

@Override public void setBeanName(String name) { [Link]("3. BeanNameAware: {}

@PostConstruct
public void postConstruct() { [Link]("7. @PostConstruct"); }

@Override public void afterPropertiesSet() { [Link]("8. afterPropertiesSet");

@PreDestroy
public void preDestroy() { [Link]("12. @PreDestroy"); }

@Override public void destroy() { [Link]("13. [Link]"); }


}
Bean Scopes — all types with examples Med

Scope Instances Lifecycle


singleton 1 per ApplicationContext Created at startup, destroyed at shutdown
prototype New per Created on demand, Spring does NOT destroy
injection/getBean()
request 1 per HTTP request Created on request arrival, destroyed when
response committed
session 1 per HTTP session Tied to HttpSession lifecycle
application 1 per ServletContext Like singleton but per web app context
PROTOTYPE BEAN IN A SINGLETON — THE BUG
@Component
@Scope("prototype")
public class TaskProcessor { // stateful — needs new instance each use
private List<String> results = new ArrayList<>();
}

@Service
public class JobService {
@Autowired
private TaskProcessor processor; // ❌ SAME prototype injected once — shared st
}

FIX WITH OBJECTPROVIDER


@Service
public class JobService {
@Autowired
private ObjectProvider<TaskProcessor> processorProvider;

public void runJob() {


TaskProcessor processor = [Link](); // ✅ fresh instan
[Link]();
}
}
5. Dependency Injection
IoC, circular dependencies, lazy loading

How does Spring resolve circular dependencies? Hard

Spring uses a 3-level cache in DefaultSingletonBeanRegistry to handle circular dependencies


for singleton beans with setter/field injection:
Level 1 — singletonObjects: Fully initialized, ready-to-use beans
Level 2 — earlySingletonObjects: Early exposed (not fully initialized), still being wired
Level 3 — singletonFactories: Factory lambdas that can produce early bean references
A → B → A RESOLUTION FLOW
// A depends on B, B depends on A
@Service public class ServiceA { @Autowired ServiceB b; }
@Service public class ServiceB { @Autowired ServiceA a; }

// Step 1: Create ServiceA instance — not fully initialized yet


// Step 2: Put ServiceA factory in level-3 cache
// Step 3: Start creating ServiceB (A's dependency)
// Step 4: ServiceB needs ServiceA → found in level-3 cache → early ref returned
// Step 5: ServiceB finishes initialization → moved to level-1 cache
// Step 6: ServiceA injects ServiceB → ServiceA fully initialized → level-1 cache

⚠️ Constructor injection with circular dep always fails — Spring can't create A without B, and B
without A. Spring Boot 2.6+ also detects and blocks circular deps by default. Fix: break the cycle by
extracting shared logic into a third bean, or use @Lazy on one side.
BREAKING CIRCULAR DEP WITH @LAZY
@Service
public class ServiceA {
private final ServiceB b;
public ServiceA(@Lazy ServiceB b) { // inject proxy, resolve B lazily
this.b = b;
}
}

6. JPA & Hibernate


ORM internals, N+1, caching, entity states

JPA Entity States — lifecycle with code Hard

State In PersistenceContext? DB-synced?


Transient No No
Managed (Persistent) Yes Yes — dirty checked at flush
Detached No Changes NOT tracked
Removed Yes (briefly) DELETE on flush

EntityManager em = ...;

// TRANSIENT — no id, no persistence context


User user = new User("Alice");

// MANAGED — entity tracked. Any field change is auto-flushed to DB!


[Link](user);
[Link]("alice@[Link]"); // UPDATE will be issued automatically

// DETACHED — session closed, changes not tracked


[Link](user); // or [Link]() or end of @Transactional
[Link]("new@[Link]"); // NOT synced to DB

// Re-attach with merge — returns a NEW managed copy


User managedUser = [Link](user); // SELECT + UPDATE

// REMOVED
[Link](managedUser); // DELETE on next flush

⚠️ Dirty checking trap: If you load an entity, modify it, and the method is @Transactional —
Hibernate will issue an UPDATE automatically at flush time even without calling save() ! This
surprises many developers.
N+1 problem — causes, detection, all fixes Tricky

THE PROBLEM
@Entity
public class Order {
@ManyToOne(fetch = [Link])
private Customer customer; // lazy — loaded on access
}

// BAD: 1 query for orders + N queries for each customer


List<Order> orders = [Link](); // SELECT * FROM orders (1 query)
[Link](o -> [Link]([Link]().getName())); // N queries!

FIX 1: JPQL JOIN FETCH

@Query("SELECT o FROM Order o JOIN FETCH [Link] WHERE [Link] = :status")


List<Order> findByStatusWithCustomer(@Param("status") String status);
// 1 JOIN query → no lazy loads at all

FIX 2: @ENTITYGRAPH
@EntityGraph(attributePaths = { "customer", "items" })
List<Order> findByStatus(String status); // Spring Data JPA — no JPQL needed

FIX 3: @BATCHSIZE (REDUCES TO N/BATCHSIZE QUERIES)

@OneToMany(mappedBy = "order")
@BatchSize(size = 30) // Hibernate loads 30 collections in one IN clause
private List<OrderItem> items;

💡 Detect N+1 in tests with Hypersistence Optimizer or by enabling:


[Link].generate_statistics=true and checking query count
in logs.
L1 vs L2 Cache in Hibernate — detailed Hard

L1 CACHE — SESSION (ENTITYMANAGER) SCOPED


// L1 cache in action — within same transaction/session
User u1 = [Link]([Link], 1L); // SELECT FROM users WHERE id=1
User u2 = [Link]([Link], 1L); // NO SQL — returns from L1 cache
[Link](u1 == u2); // true — same object reference!

L2 CACHE — SESSIONFACTORY SCOPED, SHARED ACROSS SESSIONS


# [Link]
[Link].use_second_level_cache=true
[Link].factory_class=\
[Link]
[Link]=[Link]

@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) // enable L2 for this entity
public class Product {
@Id private Long id;
private String name;
}

// Session 1
[Link]([Link], 1L); // DB hit → stored in L2 cache
// Session 2 (different transaction)
[Link]([Link], 1L); // L2 cache hit → no DB query!

L1 L2
Scope Single EntityManager Entire application (SessionFactory)
Default Always on Off — must configure
Eviction Session close / [Link]() TTL, explicit eviction, or on entity update
Provider Hibernate built-in Ehcache, Caffeine, Redis via JCache
save() vs saveAndFlush() vs persist() vs merge() Tricky

// 1. persist() — JPA. Only for NEW (transient) entities. Cannot handle detached.
[Link](new User("Alice")); // INSERT queued until flush

// 2. merge() — JPA. Returns a NEW managed copy. Input object stays detached.
User detached = ...; [Link]("Bob");
User managed = [Link](detached); // SELECT + UPDATE; detached unchanged
// Always use 'managed' after merge, not 'detached'!

// 3. Spring Data JPA save() — smart: persist if new (no ID), merge if existing
[Link](new User("Charlie")); // → persist
[Link](existingUser); // → merge (has ID)

// 4. saveAndFlush() — save + immediately write to DB (useful in same-tx tests)


User saved = [Link](user); // DB write now, not at tx commit

💡 isNew() detection in Spring Data: entity has no ID → new → persist. Has ID → merge. Override
with Persistable<ID> for custom logic.

7. Transactions
Propagation, isolation, rollback rules, tricky gotchas
All @Transactional propagation types with examples Hard

Propagation Behaviour
REQUIRED Join existing tx; create new if none exists
(default)
REQUIRES_NEW Always create NEW tx; suspend existing tx for duration
SUPPORTS Join if tx exists; run non-transactionally if none
NOT_SUPPORTED Always run non-transactionally; suspend existing tx
MANDATORY Must have active tx; throw IllegalTransactionStateException if
none
NEVER Must NOT have tx; throw exception if tx exists
NESTED Runs in nested tx (savepoint); partial rollback possible; outer tx
commits/rolls back independently
REQUIRES_NEW — AUDIT LOG THAT MUST PERSIST EVEN IF MAIN TX ROLLS BACK
@Service
public class OrderService {
@Autowired AuditService auditService;

@Transactional
public void placeOrder(Order order) {
saveOrder(order);
[Link]("ORDER_PLACED"); // separate tx — persists even if this ro
if (paymentFailed) throw new PaymentException(); // rolls back order but NO
}
}

@Service
public class AuditService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String event) {
[Link](new AuditLog(event)); // own tx — commits independently
}
}

NESTED — SAVEPOINT WITHIN OUTER TX


@Transactional
public void processItems(List<Item> items) {
for (Item item : items) {
try {
[Link](item); // NESTED — savepoint before each item
} catch (Exception e) {
// roll back only this item's savepoint, continue outer tx
}
}
} // outer tx commits all successfully processed items

Transaction Isolation Levels — what each prevents Hard

Non- Phantom
Level Dirty Read Repeatable Read Performance
Read
READ_UNCOMMITTED ❌ allowed ❌ allowed ❌ allowed Fastest
READ_COMMITTED (PG ✅ ❌ allowed ❌ allowed Fast
default) prevented
REPEATABLE_READ ✅ ✅ prevented ❌ allowed Medium
(MySQL default) prevented
SERIALIZABLE ✅ ✅ prevented ✅ Slowest
prevented prevented
Dirty Read: Tx A reads uncommitted data from Tx B. Tx B rolls back → A read garbage.
Non-Repeatable Read: Tx A reads row twice; Tx B updates it between reads → different values.
Phantom Read: Tx A runs same query twice; Tx B inserts row matching query → different row
counts.
@Transactional(isolation = Isolation.REPEATABLE_READ)
public BigDecimal calculateBalance(long accountId) {
BigDecimal credits = [Link](accountId);
BigDecimal debits = [Link](accountId);
// Without REPEATABLE_READ, another tx could insert a row between queries
return [Link](debits);
}
@Transactional rollback rules — all tricky cases Tricky

DEFAULT: ROLLS BACK ON RUNTIMEEXCEPTION AND ERROR ONLY


@Transactional
public void doWork() throws IOException {
[Link](data);
throw new IOException("file not found"); // ❌ Checked exception — tx COMMITS!
}

@Transactional(rollbackFor = [Link]) // ✅ rollback on any exception


public void doWorkSafe() throws IOException { ... }

@Transactional(noRollbackFor = [Link]) // don't rollback for


public void fulfillOrder() { ... }

TRAP: SWALLOWED EXCEPTION → NO ROLLBACK


@Transactional
public void processPayment() {
try {
chargeCard();
} catch (PaymentException e) {
[Link]("payment failed", e);
// ❌ Exception swallowed — Spring doesn't know it failed → tx COMMITS!
}
}

// Fix: mark tx as rollback-only manually if not rethrowing


@Transactional
public void processPaymentFixed(TransactionStatus status) {
try {
chargeCard();
} catch (PaymentException e) {
[Link]("payment failed", e);
[Link]().setRollbackOnly(); // ✅
}
}

8. Spring Security
Authentication, authorization, filter chain, JWT
Spring Security Filter Chain — detailed flow Hard

Request → DelegatingFilterProxy → FilterChainProxy →


Matches SecurityFilterChain by URL → SecurityContextPersistenceFilter →
UsernamePasswordAuthFilter or BearerTokenFilter → ExceptionTranslationFilter →
AuthorizationFilter → Controller
MODERN SPRING SECURITY 6 CONFIGURATION

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable) // stateless JWT API
.sessionManagement(sm -> sm
.sessionCreationPolicy([Link]))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.addFilterBefore(jwtAuthFilter, [Link]
.exceptionHandling(ex -> ex
.authenticationEntryPoint(customEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler))
.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12); // strength 12 — ~250ms per hash
}
}
JWT Authentication — full implementation Hard

JWT FILTER
@Component
@RequiredArgsConstructor
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {

String header = [Link]("Authorization");


if (header == null || ![Link]("Bearer ")) {
[Link](request, response); return;
}

String token = [Link](7);


String username = [Link](token);

if (username != null && [Link]().getAuthenticatio


UserDetails userDetails = [Link](usernam
if ([Link](token, userDetails)) {
var auth = new UsernamePasswordAuthenticationToken(
userDetails, null, [Link]());
[Link](new WebAuthenticationDetailsSource().buildDetails(r
[Link]().setAuthentication(auth);
}
}
[Link](request, response);
}
}

JWT UTILITY
@Component
public class JwtUtil {
@Value("${[Link]}") private String secret;
@Value("${[Link]}") private long expiryMs; // 24h default

public String generateToken(UserDetails userDetails) {


return [Link]()
.subject([Link]())
.claim("roles", [Link]())
.issuedAt(new Date())
.expiration(new Date([Link]() + expiryMs))
.signWith([Link]([Link]()))
.compact();
}

public boolean isValid(String token, UserDetails userDetails) {


return extractUsername(token).equals([Link]())
&& !isExpired(token);
}
}
@PreAuthorize vs @Secured vs @RolesAllowed — with SpEL examples Med

@RestController
@RequestMapping("/api/orders")
public class OrderController {

// @PreAuthorize — most powerful, full SpEL support


@GetMapping
@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
public List<Order> getOrders() { ... }

// Access method parameters in SpEL


@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN') or @[Link](#id, authentication)"
public Order update(@PathVariable long id, @RequestBody Order order) { ... }

// @PostAuthorize — check AFTER method runs (can inspect return value)


@GetMapping("/{id}")
@PostAuthorize("[Link] == [Link]")
public Order getById(@PathVariable long id) { ... }

// @Secured — simple, no SpEL, requires ROLE_ prefix


@DeleteMapping("/{id}")
@Secured("ROLE_ADMIN")
public void delete(@PathVariable long id) { ... }
}

Enable with @EnableMethodSecurity (Spring Security 6+). This replaces deprecated


@EnableGlobalMethodSecurity .

9. Application Properties & Configuration


Configuration files, profiles, externalized config
Property source precedence — all 12 levels Hard

Spring Boot evaluates properties in this order (higher = wins). Lower-numbered = higher priority:
1. Command-line arguments: --[Link]=9090
2. SPRING_APPLICATION_JSON (env var with embedded JSON)
3. Servlet config init params ( [Link] )
4. Servlet context init params
5. JNDI attributes from java:comp/env
6. Java system properties ( -[Link]=9090 )
7. OS environment variables ( SERVER_PORT=9090 )
8. Profile-specific config outside jar: [Link]
9. Config files outside jar: [Link]
10. Profile-specific config inside jar: [Link]
11. Config files inside jar: [Link]
12. @PropertySource annotations on @Configuration classes
💡 OS env variables use relaxed binding: SERVER_PORT maps to [Link] ,
SPRING_DATASOURCE_URL maps to [Link] . This is how Kubernetes/Docker
secrets work.
Spring Profiles — complete guide Med

PROFILE-SPECIFIC FILES
# [Link] — base config (always loaded)
spring:
application:
name: my-service
server:
port: 8080

---
# [Link]
spring:
datasource:
url: jdbc:h2:mem:devdb
jpa:
[Link]-auto: create-drop
[Link]: DEBUG

---
# [Link]
spring:
datasource:
url: jdbc:postgresql://${DB_HOST}:5432/${DB_NAME}
username: ${DB_USER}
password: ${DB_PASS}
jpa:
[Link]-auto: validate
[Link]: WARN

PROFILE-CONDITIONAL BEANS
@Configuration
public class StorageConfig {

@Bean
@Profile("dev")
public StorageService localStorageService() {
return new LocalFileStorageService("/tmp/uploads");
}

@Bean
@Profile("prod")
public StorageService s3StorageService() {
return new S3StorageService(awsConfig);
}
}

ACTIVATION METHODS
# In [Link]
[Link]=prod

# Command line
java -jar [Link] --[Link]=prod

# Environment variable (Docker/K8s)


SPRING_PROFILES_ACTIVE=prod

# Profile groups (activate multiple at once)


[Link]=prod,metrics,featureflags
@ConfigurationProperties — full example with validation Med

# [Link]
app:
email:
host: [Link]
port: 587
username: ${SMTP_USER}
password: ${SMTP_PASS}
retry-attempts: 3
retry-delay: PT5S # ISO-8601 Duration
allowed-domains:
- [Link]
- [Link]

@ConfigurationProperties(prefix = "[Link]")
@Validated
@Component
public class EmailProperties {

@NotBlank
private String host;

@Min(1) @Max(65535)
private int port;

@NotBlank
private String username;

private String password;

@Min(1)
private int retryAttempts = 3;

private Duration retryDelay = [Link](5); // auto-converted from PT5

@NotEmpty
private List<String> allowedDomains;

// getters/setters or use @Data from Lombok


}
10. Actuator & Monitoring
Production-ready endpoints and health monitoring

Spring Boot Actuator — endpoints and security Med

Endpoint Description Sensitive?


/actuator/health Aggregated health: DB, disk, custom checks Partial
/actuator/metrics/{name} Micrometer metrics (JVM, HTTP, custom) Yes
/actuator/env All resolved properties (masks passwords) Yes
/actuator/beans All Spring beans + their dependencies Yes
/actuator/mappings All URL-to-handler mappings Yes
/actuator/loggers View and change log level at runtime Yes
/actuator/threaddump Current thread dump Yes
/actuator/heapdump Download heap dump Yes

# Only expose health and info publicly, secure rest


[Link]=health,info,metrics,loggers
[Link]=heapdump,threaddump
[Link]-details=when-authorized
[Link]=8081 # separate port for actuator — don't expose to interne

CHANGE LOG LEVEL AT RUNTIME (NO RESTART!)


# POST /actuator/loggers/[Link]
{ "configuredLevel": "DEBUG" }
Custom HealthIndicator and custom Metric Med

CUSTOM HEALTHINDICATOR
@Component
public class ExternalApiHealth implements HealthIndicator {
private final RestTemplate restTemplate;

@Override
public Health health() {
try {
ResponseEntity<String> resp = [Link]("[Link]
if ([Link]().is2xxSuccessful())
return [Link]().withDetail("latency", "ok").build();
return [Link]().withDetail("status", [Link]()).build()
} catch (Exception e) {
return [Link](e).withDetail("error", [Link]()).build();
}
}
}
// Exposed at: /actuator/health/externalApi

CUSTOM MICROMETER METRIC


@Service
public class OrderService {
private final Counter orderCounter;
private final Timer processingTimer;

public OrderService(MeterRegistry registry) {


orderCounter = [Link]("[Link]", "app", "my-service");
processingTimer = [Link]("[Link]");
}

public Order createOrder(OrderRequest req) {


return [Link](() -> {
[Link]();
return doCreate(req);
});
}
}
11. AOP — Aspect Oriented Programming
Proxies, pointcuts, advice types
AOP core concepts with a full working example Med

Term Meaning
Aspect Module that encapsulates cross-cutting concern ( @Aspect class)
Advice Code to run: @Before , @After , @Around , @AfterReturning ,
@AfterThrowing

Join Point Point in execution — in Spring AOP, always a method execution


Pointcut Expression selecting which join points to advise
Target The original bean being advised (wrapped in proxy)
Object
Weaving Applying aspects — Spring does runtime proxy weaving

@Aspect
@Component
public class AuditAspect {

// Pointcut: all methods in service package


@Pointcut("execution(* [Link].*.*(..))")
public void serviceLayer() {}

// Before — runs before method


@Before("serviceLayer()")
public void logEntry(JoinPoint jp) {
[Link]("Calling {} with args {}", [Link]().getName(), [Link]
}

// AfterReturning — inspect return value


@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void logResult(JoinPoint jp, Object result) {
[Link]("{} returned {}", [Link]().getName(), result);
}

// AfterThrowing — inspect exception


@AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
public void logError(JoinPoint jp, Exception ex) {
[Link]("{} threw {}", [Link]().getName(), [Link]());
}

// Around — most powerful, controls execution


@Around("@annotation([Link])")
public Object retryOnFailure(ProceedingJoinPoint pjp) throws Throwable {
int attempts = 0;
while (true) {
try {
return [Link](); // invoke actual method
} catch (Exception e) {
if (++attempts >= 3) throw e;
[Link](1000L * attempts);
}
}
}
}
JDK Dynamic Proxy vs CGLIB — detailed difference Hard

JDK DYNAMIC PROXY


// Works ONLY if target implements an interface
public interface PaymentService { void pay(); }

@Service
public class PaymentServiceImpl implements PaymentService { ... }

// Spring creates: [Link]([Link], handler)


// Injecting as interface → JDK proxy used
// ✅ @Autowired PaymentService payment;
// ❌ @Autowired PaymentServiceImpl payment; — ClassCastException if JDK proxy

CGLIB PROXY

// Subclasses the class directly — no interface needed


@Service
public class OrderService { // no interface
public void createOrder() { ... }
}
// Spring Boot default: proxyTargetClass=true → always uses CGLIB
// CGLIB creates: class OrderService$$SpringCGLIB extends OrderService { ... }

// ❌ CGLIB cannot proxy these:


public final class FinalService { ... } // can't subclass final
public final void doSomething() { ... } // can't override final method

⚠️ Never make Spring-managed beans or their important methods final — CGLIB proxy will fail
silently or throw an error.

12. Async & Scheduling


@Async, @Scheduled, ThreadPoolTaskExecutor
@Async — internals, thread pool config, CompletableFuture Tricky

PROPER THREAD POOL CONFIGURATION


@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
[Link](5); // always-alive threads
[Link](20); // max threads under load
[Link](100); // tasks queued before new threads spawne
[Link](60); // idle threads above core die after 60s
[Link]("async-");
[Link](new CallerRunsPolicy()); // don't drop tas
[Link]();
return exec;
}

@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) ->
[Link]("Async error in {}: {}", [Link](), [Link]());
}
}

USAGE WITH COMPLETABLEFUTURE


@Service
public class ReportService {

@Async
public CompletableFuture<Report> generateReport(long userId) {
// runs in thread pool, not caller thread
Report report = expensiveCalculation(userId);
return [Link](report);
}
}

// Parallel async calls


CompletableFuture<Report> salesFuture = [Link](1);
CompletableFuture<Report> stockFuture = [Link](2);
[Link](salesFuture, stockFuture).join(); // wait for both
Report sales = [Link]();
Report stock = [Link]();

@Scheduled — all modes with cron examples Med

@Component
public class ScheduledTasks {

// fixedRate: fires every 5s regardless of last execution duration


@Scheduled(fixedRate = 5000)
public void heartbeat() { [Link]("ping"); }

// fixedDelay: 5s AFTER previous execution finishes


@Scheduled(fixedDelay = 5000, initialDelay = 10000) // start after 10s
public void cleanupTemp() { deleteOldFiles(); }

// Cron: "sec min hour day month weekday"


@Scheduled(cron = "0 0 2 * * *") // every day at 02:00
public void nightly() { runNightlyBatch(); }

@Scheduled(cron = "0 0 9 * * MON-FRI") // 9AM weekdays


public void dailyReport() { sendReport(); }

@Scheduled(cron = "0 0/30 8-18 * * MON-FRI") // every 30min, 8AM–6PM weekdays


public void businessHoursCheck() { checkInventory(); }

// From properties — flexible


@Scheduled(cron = "${[Link] 0 * * * *}")
public void sync() { syncData(); }
}

⚠️ Default scheduler is single-threaded. If one task is slow, others queue up. Fix:
[Link]=5

13. Testing
Unit tests, slice tests, Mockito integration
@SpringBootTest vs slice test annotations — when to use each Med

@WEBMVCTEST — CONTROLLER LAYER ONLY, NO DB


@WebMvcTest([Link])
class OrderControllerTest {

@Autowired MockMvc mockMvc;


@MockBean OrderService orderService; // service mocked — not in web slice

@Test
void createOrder_returns201() throws Exception {
var order = new Order(1L, "PENDING");
given([Link](any())).willReturn(order);

[Link](post("/api/orders")
.contentType(MediaType.APPLICATION_JSON)
.content("""{"productId":"P1","quantity":2}"""))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(1));
}
}

@DATAJPATEST — JPA/REPOSITORY LAYER, H2 IN-MEMORY

@DataJpaTest
class UserRepositoryTest {

@Autowired UserRepository userRepo;


@Autowired TestEntityManager em; // helper for test data setup

@Test
void findByEmail_returnsUser() {
[Link](new User("alice@[Link]"));
Optional<User> found = [Link]("alice@[Link]");
assertThat(found).isPresent();
assertThat([Link]().getEmail()).isEqualTo("alice@[Link]");
}
}

Annotation Context loaded Speed


@SpringBootTest Full context Slow
@WebMvcTest Web layer (controllers, filters, advice) Fast
@DataJpaTest JPA layer + H2 Fast
@RestClientTest RestTemplate/WebClient Fast
No annotation (plain) No context — pure Mockito Fastest
@MockBean vs @Mock vs @SpyBean — differences Tricky

@MOCK — PURE MOCKITO, NO SPRING CONTEXT


@ExtendWith([Link])
class OrderServiceTest {

@Mock PaymentService paymentService; // Mockito mock


@InjectMocks OrderService orderService; // inject mocks into this

@Test
void placeOrder_callsPayment() {
when([Link](any())).thenReturn(true);
[Link](new Order());
verify(paymentService).charge(any());
}
}

@MOCKBEAN — REPLACES BEAN IN SPRING CONTEXT


@SpringBootTest
class OrderIntegrationTest {

@Autowired OrderService orderService;


@MockBean PaymentService paymentService; // replaces real bean in context
@MockBean EmailService emailService;

@Test
void fullFlowTest() {
when([Link](any())).thenReturn(true);
var result = [Link](new Order());
assertThat([Link]()).isEqualTo("CONFIRMED");
}
}

@SPYBEAN — REAL BEAN + ABILITY TO OVERRIDE SPECIFIC METHODS

@SpringBootTest
class NotificationTest {

@SpyBean EmailService emailService; // REAL bean, but spy on it

@Test
void sendEmail_calledOnce() {
doNothing().when(emailService).sendEmail(any()); // stub specific method
[Link](order);
verify(emailService, times(1)).sendEmail(any());
}
}

14. Misc & Tricky Questions


Exception handling, events, caching, and more
@ControllerAdvice — global exception handling with problem details Med

@RestControllerAdvice
public class GlobalExceptionHandler {

// Handle custom business exception


@ExceptionHandler([Link])
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(ResourceNotFoundException ex, WebRequest re
return new ErrorResponse("NOT_FOUND", [Link](), [Link](f
}

// Handle validation errors (@Valid failures)


@ExceptionHandler([Link])
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleValidation(MethodArgumentNotValidException ex)
Map<String, String> errors = [Link]().getFieldErrors().stream(
.collect([Link](
FieldError::getField,
FieldError::getDefaultMessage));
return [Link]("status", 400, "errors", errors);
}

// Catch-all
@ExceptionHandler([Link])
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleAll(Exception ex) {
[Link]("Unhandled exception", ex);
return new ErrorResponse("INTERNAL_ERROR", "An unexpected error occurred",
}
}
Spring Caching — @Cacheable, @CachePut, @CacheEvict in depth Med

@Service
public class ProductService {

// Cache result. Key = productId. On cache hit: method NOT called.


@Cacheable(value = "products", key = "#productId")
public Product findById(long productId) {
[Link]("DB hit for product {}", productId); // won't print on cache hit
return [Link](productId).orElseThrow();
}

// Conditional caching — only cache active products


@Cacheable(value = "products", key = "#id", condition = "#id > 0", unless = "#r
public Product findConditional(long id) { ... }

// Always update cache after save (write-through)


@CachePut(value = "products", key = "#[Link]")
public Product save(Product product) {
return [Link](product); // method ALWAYS runs, cache ALWAYS updat
}

// Remove from cache on delete


@CacheEvict(value = "products", key = "#productId")
public void delete(long productId) { [Link](productId); }

// Clear entire cache


@CacheEvict(value = "products", allEntries = true)
public void clearAll() {}

// Multiple cache operations


@Caching(evict = {
@CacheEvict(value = "products", key = "#id"),
@CacheEvict(value = "productList", allEntries = true)
})
public void deleteAndClearList(long id) { ... }
}

REDIS CACHE CONFIG


@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
var config = [Link]()
.entryTtl([Link](10))
.disableCachingNullValues()
.serializeValuesWith([Link]
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return [Link](factory).cacheDefaults(config).build();
}
}
Spring Application Events — lifecycle and custom events Med

BUILT-IN STARTUP EVENTS (IN ORDER)


1. ApplicationStartingEvent — very early, logging/listeners only
2. ApplicationEnvironmentPreparedEvent — environment ready, not context yet
3. ApplicationContextInitializedEvent — context created, not refreshed
4. ApplicationPreparedEvent — beans definitions loaded, not instantiated
5. ContextRefreshedEvent — all beans instantiated
6. ApplicationStartedEvent — app started, before runners
7. ApplicationReadyEvent — ready to serve traffic (after
CommandLineRunner/ApplicationRunner)
8. ApplicationFailedEvent — startup failed
CUSTOM DOMAIN EVENTS
// Event class
public record OrderPlacedEvent(Order order, Instant timestamp) {}

// Publisher
@Service
@RequiredArgsConstructor
public class OrderService {
private final ApplicationEventPublisher publisher;

@Transactional
public Order placeOrder(OrderRequest req) {
Order order = [Link](new Order(req));
[Link](new OrderPlacedEvent(order, [Link]())); // sync
return order;
}
}

// Listener — same thread (transactional event)


@Component
public class OrderEventListener {

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onOrderPlaced(OrderPlacedEvent event) {
// runs ONLY after transaction commits — prevents sending email if tx rolls
[Link]([Link]());
}

@Async
@EventListener
public void updateAnalytics(OrderPlacedEvent event) {
[Link](event); // async — doesn't block main thread
}
}

💡 Use @TransactionalEventListener instead of @EventListener for events published


inside @Transactional — ensures listener only fires after the transaction successfully commits.

@Retryable — Spring Retry with backoff strategies Med

@Configuration
@EnableRetry
public class RetryConfig {}

@Service
public class ExternalApiService {

// Retry 3x with exponential backoff: 1s, 2s, 4s


@Retryable(
retryFor = { [Link], [Link]
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2, maxDelay = 10000)
)
public ApiResponse callApi(String endpoint) {
[Link]("Attempting API call to {}", endpoint);
return [Link](endpoint, [Link]);
}

// Fallback — called after ALL retries exhausted


@Recover
public ApiResponse recoverApiCall(Exception ex, String endpoint) {
[Link]("All retries failed for {}: {}", endpoint, [Link]());
return [Link](); // or throw, or return cached data
}
}

⚠️ @Recover method must have the same return type and the first parameter must be the
exception type. It also works via AOP proxy — same self-invocation limitation applies.
CommandLineRunner vs ApplicationRunner vs Easy
@EventListener(ApplicationReadyEvent)

COMMANDLINERUNNER — RAW ARGS AS STRING[]


@Component
@Order(1) // run first
public class DatabaseSeeder implements CommandLineRunner {
@Override
public void run(String... args) {
if ([Link] > 0 && args[0].equals("--seed")) seedDatabase();
}
}

APPLICATIONRUNNER — PARSED ARGS


@Component
@Order(2)
public class CacheWarmer implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
if ([Link]("warm-cache")) warmCache();
// [Link](), [Link]("profile")
}
}

APPLICATIONREADYEVENT — FOR NON-STARTUP TASKS (NO ORDERING)


@EventListener([Link])
public void onReady() {
registerWithServiceDiscovery(); // register in Eureka/Consul after full startup
}
Graceful shutdown — how it works and configuration Med

[Link]=graceful
[Link]-per-shutdown-phase=30s

SHUTDOWN SEQUENCE
SIGTERM received → Stop accepting new requests (return 503) →
Wait for in-flight requests (up to 30s) → @PreDestroy / DisposableBean called →
Thread pools shut down → Context closed → JVM exits

// Custom shutdown hook


@Component
public class ShutdownHandler {
@PreDestroy
public void onShutdown() {
[Link]("Application shutting down — flushing queues...");
[Link]();
[Link]();
}
}

In Kubernetes: set terminationGracePeriodSeconds in Pod spec to match or exceed


timeout-per-shutdown-phase so K8s doesn't SIGKILL before graceful shutdown completes.

You might also like