// Java Interview — Senior Level

⚠️ PART 5: Exception Handling (Q151–175)

Exception hierarchy, checked vs unchecked, try-with-resources, custom exceptions, best practices.

58 Questions
10 Mid
43 Advanced
5 Expert
151 . Checked vs unchecked exceptions? MID

Checked exceptions (must be caught or declared with throws):

  • Extends Exception but NOT RuntimeException
  • Represent recoverable, expected conditions
  • Examples: IOException, SQLException, ClassNotFoundException
public void readFile(String path) throws IOException { // must declare
    Files.readAllBytes(Path.of(path));
}

Unchecked exceptions (RuntimeException subclasses):

  • No requirement to catch or declare
  • Represent programming errors or unrecoverable situations
  • Examples: NullPointerException, IllegalArgumentException, ArrayIndexOutOfBoundsException

Controversy: Checked exceptions are unique to Java. Many argue they clutter APIs and encourage catch (Exception e) {} anti-patterns. Spring and most modern libraries convert checked to unchecked. Kotlin and C# don't have checked exceptions.

152 . Error vs Exception? EXPERT
Throwable
├── Error          (serious JVM/system problems, don't catch)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   ├── VirtualMachineError
│   └── AssertionError
└── Exception      (application-level problems)
    ├── IOException (checked)
    ├── SQLException (checked)
    └── RuntimeException (unchecked)
        ├── NullPointerException
        ├── IllegalArgumentException
        └── ...

Errors: Abnormal conditions in JVM itself. Not caused by application code. Recovery is impossible or impractical. Don't catch Errors (except in very specific frameworks like test runners catching AssertionError).

Exceptions: Problems in application logic. Checked = expected, recoverable. Unchecked = programming errors, typically unrecoverable in context.

153 . Why RuntimeException exists? MID

RuntimeException provides a category for programming errors that:

  1. Can occur at any point in code
  2. Would be impractical to declare on every method
  3. Typically indicate bugs rather than external conditions

If all runtime errors were checked:

  • NullPointerException would have to be declared on every method that uses an object reference
  • ClassCastException on every cast
  • This would make Java code completely unmanageable

RuntimeException says: "This is the caller's fault for misusing the API. Handle it at a high level or let the application fail."

154 . Custom exception? ADVANCED
// Custom checked exception
public class UserNotFoundException extends Exception {
    private final Long userId;
    
    public UserNotFoundException(Long userId) {
        super("User not found with ID: " + userId);
        this.userId = userId;
    }
    
    // Exception chaining
    public UserNotFoundException(Long userId, Throwable cause) {
        super("User not found with ID: " + userId, cause);
        this.userId = userId;
    }
    
    public Long getUserId() { return userId; }
}

// Custom unchecked exception
public class InsufficientFundsException extends RuntimeException {
    private final BigDecimal required;
    private final BigDecimal available;
    
    public InsufficientFundsException(BigDecimal required, BigDecimal available) {
        super(String.format("Required: %s, Available: %s", required, available));
        this.required = required;
        this.available = available;
    }
}

Best practices:

  • Provide constructors with message and cause (for chaining)
  • Add relevant context fields
  • Make unchecked unless caller can meaningfully recover
  • Declare serialVersionUID for serializable exceptions
155 . Best practices for exception handling? ADVANCED
  1. Be specific: Catch specific exceptions, not Exception or Throwable
  2. Don't swallow: Never catch (Exception e) {} – at minimum log the exception
  3. Preserve cause: Always use exception chaining when wrapping: new ServiceException("msg", originalException)
  4. Fail fast: Throw early when preconditions fail (IllegalArgumentException, NullPointerException)
  5. Document with @throws: In Javadoc, document what exceptions and when
  6. Don't use exceptions for control flow: try { parse() } catch (NumberFormatException) as normal flow is bad
  7. Clean up in finally/try-with-resources: Don't rely on exception path to close resources
  8. Log once: Log at the point of handling, not at every re-throw
  9. Don't catch what you can't handle: Let it propagate to a level that can handle it
  10. RuntimeException for API misuse: IllegalArgument, IllegalState, NullPointer for programming errors
156 . Exception propagation? ADVANCED

Exceptions propagate up the call stack until caught or the program terminates.

main() → serviceMethod() → daoMethod() → throws IOException
         ↑ propagates
serviceMethod() → if not caught, propagates to main()
main() → if not caught, JVM prints stack trace and exits

Checked exceptions: Must be caught or declared at each level in the call stack.

Unchecked: Propagate automatically without declaration.

void dao() throws IOException { throw new IOException("DB Error"); }
void service() throws IOException { dao(); } // propagates
void controller() {
    try { service(); }
    catch (IOException e) { throw new ServiceException("Failed", e); } // wrap and re-throw
}
157 . What is stack trace? MID

A stack trace is a snapshot of the call stack at the moment an exception was thrown:

java.lang.NullPointerException: Cannot invoke "String.length()" because "str" is null
    at com.example.UserService.processName(UserService.java:45)
    at com.example.UserController.handleRequest(UserController.java:23)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:897)
    ...

Reading a stack trace:

  • First line: Exception type and message
  • Second line: Immediate location where exception was thrown
  • Subsequent lines: Call chain (most recent first)

Java 14+ Helpful NullPointerExceptions: JVM can tell you exactly which part of an expression was null.

Performance: new Exception() captures stack trace (expensive). new RuntimeException(message, cause, false, false) disables stack trace collection (use in high-performance exception factories).

158 . finally block behavior? ADVANCED

finally block always executes after try/catch, regardless of:

  • Normal completion
  • Exception thrown (caught or not)
  • return or break in try block
int test() {
    try {
        return 1;
    } finally {
        System.out.println("finally runs"); // prints before return
        // return 2; // DON'T: this swallows the original return/exception
    }
}

Exceptions: System.exit(), JVM crash, thread kill – finally may NOT run.

try-with-resources is preferred over finally for resource cleanup (simpler, handles suppressed exceptions better).

159 . Return in try/finally? ADVANCED

If both try and finally have return:

String test() {
    try {
        return "try"; // scheduled
    } finally {
        return "finally"; // overrides! "try" return is LOST
    }
}
test(); // returns "finally"

Similarly, if finally throws an exception, it overrides the try-block exception (original is lost unless captured and attached as suppressed).

Best practice: Never put return or throw in finally. It masks exceptions and makes code hard to reason about.

160 . Suppressed exceptions? ADVANCED

In try-with-resources, if both body and close() throw exceptions, the close() exception is suppressed and attached to the primary exception:

try (Resource r = new Resource()) {
    throw new RuntimeException("body exception");
    // r.close() also throws: "close exception"
}
// Caught: RuntimeException("body exception")
// exception.getSuppressed()[0] = RuntimeException("close exception")

Manual suppression:

Throwable primary = null;
try {
    doWork();
} catch (Throwable t) {
    primary = t;
} finally {
    try { close(); }
    catch (Throwable t2) {
        if (primary != null) primary.addSuppressed(t2);
        else throw t2;
    }
}
if (primary != null) throw primary;

try-with-resources handles this automatically – that's one reason to prefer it.

161 . What is Exception chaining? MID

Exception chaining preserves the original cause when wrapping exceptions:

try {
    statement.executeQuery(sql);
} catch (SQLException e) {
    // Wrap with context, preserve cause
    throw new DataAccessException("Failed to fetch users", e);
}

Access the chain:

catch (DataAccessException e) {
    Throwable cause = e.getCause(); // original SQLException
    cause.getCause(); // could be deeper chain
}

Constructors for chaining: Exception(String message, Throwable cause).

Why it matters: Without chaining, the original stack trace is lost. With chaining, you can trace the full execution path from root cause to high-level error.

162 . try-with-resources internals? EXPERT

Compiles to bytecode roughly equivalent to:

// try (Resource r = acquire()) { body }
Resource r = acquire();
Throwable primaryException = null;
try {
    body;
} catch (Throwable t) {
    primaryException = t;
    throw t;
} finally {
    if (r != null) {
        if (primaryException != null) {
            try { r.close(); }
            catch (Throwable t) { primaryException.addSuppressed(t); }
        } else {
            r.close();
        }
    }
}

The key difference from manual try-finally:

  1. Close exceptions are suppressed (not swallowed, not masking primary)
  2. Resource is guaranteed closed
  3. Works with multiple resources (closed in reverse order)
163 . AutoCloseable vs Closeable? MID
CloseableAutoCloseable
IntroducedJava 5Java 7
Extends--
Closeable extendsAutoCloseable-
close() throwsIOExceptionException
Multiple closesShould be idempotentNot required
Use in TWRYesYes

Closeable is for I/O resources (specifically throws IOException).

AutoCloseable is more general (throws Exception).

All java.io classes implement Closeable, which extends AutoCloseable, so all work with try-with-resources.

For custom resources: implement AutoCloseable (or Closeable for I/O). Make close() idempotent when possible (safe to call multiple times).

164 . When not to catch exception? ADVANCED
  1. You can't handle it meaningfully: Don't catch just to re-throw the same exception
  2. Let framework handle it: Spring's @ExceptionHandler, JAX-RS exception mappers
  3. Test code: Don't catch in tests – let JUnit/TestNG report failures
  4. Logging noise: Don't catch, log, and re-throw – log once at the handling point
  5. Checked exceptions in streams/lambdas: Wrapping is cumbersome; often let it propagate

Anti-pattern:

try {
    return service.process(request);
} catch (Exception e) {
    log.error("Error", e);
    throw e; // re-throwing - logger should be at higher level
}
165 . Logging exceptions correctly? ADVANCED
// CORRECT: Log with exception (full stack trace)
log.error("Failed to process order {}", orderId, exception);

// WRONG: Only log message (no stack trace)
log.error("Failed to process order {}: {}", orderId, exception.getMessage());

// WRONG: System.out.println
System.out.println("Error: " + exception); // no proper logging

// WRONG: Log and throw (duplicate logs up the chain)
log.error("Error", e);
throw new ServiceException("Error", e); // caller will log too

Structured logging:

log.error("Failed to process order", 
    kv("orderId", orderId), 
    kv("userId", userId),
    exception);

Log once: Log at the point of final handling, not at every intermediate catch-and-rethrow.

166 . Re-throwing exceptions? ADVANCED
// Rethrow same exception (no wrapping)
try { ... }
catch (IOException e) { 
    cleanup();
    throw e; // same exception, original stack trace preserved
}

// Wrap (exception translation)
try { ... }
catch (SQLException e) {
    throw new DataAccessException("DB error", e); // wrapped with cause
}

// Java 7 precise rethrow
try { mightThrowChecked(); }
catch (Exception e) {
    // compiler knows only declared checked exceptions can reach here
    throw e; // no need to declare Exception on method, only declared checked types
}
167 . Exception handling in lambdas? ADVANCED

Lambdas are tricky with checked exceptions – functional interfaces don't declare throws:

// Compile error: Function doesn't declare throws IOException
Function<String, String> fn = path -> Files.readString(Path.of(path));

// Option 1: Wrap in unchecked
Function<String, String> fn = path -> {
    try { return Files.readString(Path.of(path)); }
    catch (IOException e) { throw new UncheckedIOException(e); }
};

// Option 2: Helper method that wraps checked
@FunctionalInterface
interface CheckedFunction<T, R> { R apply(T t) throws Exception; }
static <T, R> Function<T, R> wrap(CheckedFunction<T, R> fn) {
    return t -> { try { return fn.apply(t); } catch (Exception e) { throw new RuntimeException(e); } };
}

Function<String, String> fn = wrap(path -> Files.readString(Path.of(path)));

Libraries like Lombok (@SneakyThrows), Vavr, and others provide utilities for this.

168 . Global exception handling? ADVANCED

In Spring Boot REST:

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(UserNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorResponse handleNotFound(UserNotFoundException e) {
        return new ErrorResponse("USER_NOT_FOUND", e.getMessage());
    }
    
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResponse handleValidation(ConstraintViolationException e) {
        return new ErrorResponse("VALIDATION_ERROR", buildViolationMessage(e));
    }
    
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponse handleGeneral(Exception e, HttpServletRequest request) {
        log.error("Unhandled exception for {}", request.getRequestURI(), e);
        return new ErrorResponse("INTERNAL_ERROR", "An unexpected error occurred");
    }
}

Thread.UncaughtExceptionHandler:

Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
    log.error("Uncaught exception in thread {}", thread.getName(), throwable);
    // alerting, metrics, etc.
});
169 . Performance impact of exceptions? EXPERT

Exceptions are expensive:

  1. Stack trace capture (fillInStackTrace()): Walks the JVM stack, creates StackTraceElement objects. O(depth) cost.
  2. Object creation: new Exception() involves heap allocation + GC pressure
  3. JIT deoptimization: Paths with exception handling may be compiled less aggressively

Benchmarks: A thrown/caught exception is ~100-1000x slower than a normal return, depending on stack depth.

Best practices:

  1. Don't use exceptions for control flow (e.g., use Optional instead of catching NPE)
  2. Pre-validate: Check isPresent() before get() rather than catching NoSuchElementException
  3. Expensive in tight loops: Integer.parseInt() + catch is slow; use NumberUtils.isNumeric() first
  4. Disable stack trace: For exceptions used as signals (not bugs), override fillInStackTrace():
class FastException extends RuntimeException {
    @Override public synchronized Throwable fillInStackTrace() { return this; }
}
170 . When to use assertions? ADVANCED

assert condition : "message"; – throws AssertionError if condition is false (when assertions enabled with -ea).

Appropriate uses:

  • Internal invariants: Pre-conditions for private methods (you control callers)
  • Post-conditions: Verify result after complex computation
  • Unreachable code: default: assert false : "Unknown state: " + state;
  • Class/loop invariants

NOT appropriate:

  • Public method parameter validation (use IllegalArgumentException)
  • Business logic checks (use exceptions)
  • Production code where assertions are disabled (default)

Assertions are disabled by default in production JVMs. Use IllegalArgumentException/IllegalStateException for code-correctness checks that should always run.

171 . Assertion vs exception? MID
AssertionException
UseProgrammer errors, invariantsExpected/unexpected runtime conditions
DefaultDisabled in productionAlways active
TypeAssertionError (Error)Exception subclass
Public APINo (use IAE)Yes
RecoveryNo (programming bug)Yes (if checked)

Rule of thumb: Assertions document invariants for developers. Exceptions handle conditions for callers.

172 . Custom checked exceptions – pros/cons? ADVANCED

Pros:

  • Compiler forces callers to handle/declare
  • Self-documenting API: method signature shows failure modes
  • Forces recovery strategy consideration
  • Type-safe error handling

Cons:

  • Clutters method signatures (throws A, B, C, D)
  • Callers often swallow them: catch (Exception e) { }
  • Breaks with lambdas/streams
  • Spreads checked exception declarations through layers
  • Controversy: C#, Kotlin, Scala, modern Java frameworks avoid them

Senior take: Use checked exceptions sparingly. If the caller can genuinely do something different for this exception (retry, fallback, user message), it might warrant checked. Otherwise, unchecked is cleaner. Spring's philosophy: convert all infrastructure exceptions (JDBC, JMS) to unchecked (DataAccessException hierarchy).

173 . Exception translation? ADVANCED

Converting low-level/infrastructure exceptions to higher-level/domain exceptions:

// DAO layer
try {
    return jdbcTemplate.queryForObject(sql, params, User.class);
} catch (EmptyResultDataAccessException e) {
    throw new UserNotFoundException(id); // translate to domain exception
} catch (DataAccessException e) {
    throw new DataStoreException("Failed to fetch user " + id, e); // translate
}

Why:

  1. Abstraction: Callers shouldn't need to know you use JDBC/JPA
  2. Encapsulation: Implementation details (SQL errors) don't leak
  3. Layer responsibility: DAL translates DB errors; service layer translates to business errors
  4. Testability: Can test service with mock that throws domain exceptions
174 . API design and exceptions? EXPERT
  1. Checked for expected failures: findUser()throws UserNotFoundException if not finding is a normal case callers must handle
  2. Unchecked for programming errors: setAge(-1)throw new IllegalArgumentException("Age must be non-negative")
  3. Document what you throw: Javadoc @throws
  4. Consistent naming: XxxException for checked, XxxException extends RuntimeException for unchecked, or just clear naming
  5. Don't throw from constructors (leaking partially constructed objects) unless unavoidable
  6. Don't throw from finalizers
  7. Prefer Optional over checked exception for "not found" scenarios in functional contexts
  8. Consider http-status-friendly exceptions for REST APIs
175 . Exception best practices in Spring? ADVANCED
  1. Use @ControllerAdvice / @RestControllerAdvice for centralized exception handling
  2. Transactional rollback: By default, Spring only rolls back on RuntimeException and Error. Use @Transactional(rollbackFor = Exception.class) to also rollback on checked exceptions
  3. DataAccessException: Catch Spring's DataAccessException (unchecked) not raw SQLException
  4. Don't expose internal exceptions in REST responses (security risk + poor UX)
  5. Consistent error response format: { "error": "USER_NOT_FOUND", "message": "...", "timestamp": "..." }
  6. Spring Boot's ErrorController: Customize /error endpoint for HTML error pages
  7. Validation exceptions: Handle MethodArgumentNotValidException for @Valid failures
  8. Log with correlation ID: Include request ID in exception logs for traceability
  9. Propagation in async: @Async methods lose exception context; use AsyncUncaughtExceptionHandler
  10. Circuit breaker: Use Resilience4j to handle repeated failures gracefully
// Getting context (rarely needed in Spring Boot apps)
ApplicationContext ctx = SpringApplication.run(MyApp.class, args);
UserService service = ctx.getBean(UserService.class);

// In a bean, inject context
@Component
class MyComponent implements ApplicationContextAware {
    private ApplicationContext context;
    
    @Override
    public void setApplicationContext(ApplicationContext ctx) {
        this.context = ctx;
    }
}

Hierarchy: Spring Boot creates a parent/child context. Parent context has non-web beans. Child (Web ApplicationContext) has web-specific beans (controllers, filters). Beans in parent visible to child but not vice versa.

Environment: ApplicationContext wraps Environment which holds properties, profiles, active environment.

268 . Bean lifecycle? ADVANCED

Complete bean lifecycle:

1. Instantiation (constructor called)
2. Property injection (setter/field injection, @Autowired)
3. BeanNameAware.setBeanName()
4. BeanFactoryAware.setBeanFactory()
5. ApplicationContextAware.setApplicationContext()
6. BeanPostProcessor.postProcessBeforeInitialization() [all beans]
7. @PostConstruct method
8. InitializingBean.afterPropertiesSet()
9. @Bean(initMethod = "init") custom init method
10. BeanPostProcessor.postProcessAfterInitialization() [all beans]
   → Bean ready for use
   ...
11. @PreDestroy method
12. DisposableBean.destroy()
13. @Bean(destroyMethod = "cleanup") custom destroy method

BeanPostProcessor is the extension point used by Spring AOP (creates proxies), @Async, @Transactional, etc.

269 . @Bean vs @Component? MID

@Component (and specializations @Service, @Repository, @Controller):

  • Class-level annotation
  • Detected by component scanning
  • Spring instantiates the class directly
  • For your own classes

@Bean:

  • Method-level annotation inside @Configuration class
  • You write the instantiation code
  • For third-party classes you can't annotate
  • More control over construction
// @Component - Spring creates instance
@Service
public class UserService { ... }

// @Bean - you create instance (for third-party)
@Configuration
public class AppConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return mapper;
    }
    
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder.setConnectTimeout(Duration.ofSeconds(5)).build();
    }
}

@Configuration classes use CGLIB proxying: @Bean methods called directly return the same bean instance (singleton). Use @Configuration(proxyBeanMethods = false) for lighter-weight config when inter-bean method calls not needed.

270 . Dependency injection types? ADVANCED

Constructor injection (recommended):

@Service
public class OrderService {
    private final OrderRepository repo;
    private final PaymentService payment;
    
    // @Autowired optional if single constructor (Spring 4.3+)
    public OrderService(OrderRepository repo, PaymentService payment) {
        this.repo = repo;
        this.payment = payment;
    }
}

Setter injection:

@Service
public class OrderService {
    private OrderRepository repo;
    
    @Autowired
    public void setRepo(OrderRepository repo) { this.repo = repo; }
}

Field injection (least recommended):

@Service
public class OrderService {
    @Autowired private OrderRepository repo; // not testable without Spring context
}
271 . @Autowired vs constructor injection? ADVANCED

Problems with @Autowired field injection:

  1. Not testable: Can't inject mocks without Spring context or reflection; with constructor you can new OrderService(mockRepo)
  2. Hides dependencies: Looking at constructor tells you exactly what's needed
  3. Allows circular dependencies (masks bad design); constructor injection fails fast
  4. Can't make fields final: Breaks immutability
  5. Reflection-based: Slower, bypasses normal Java visibility

Constructor injection advantages:

  • Fields can be final (immutable, fail-fast if null)
  • Obvious dependencies (API contract)
  • Works without Spring (testable in isolation)
  • Forces small, focused classes (many constructor params = code smell)
  • No circular dependencies unless you use @Lazy

Official recommendation: Constructor injection is the Spring Team's preferred approach (since Spring 4.3).

272 . Profiles? ADVANCED

Profiles allow different beans/configurations for different environments:

@Configuration
@Profile("prod")
public class ProdConfig { ... }

@Configuration
@Profile("!prod") // not prod
public class DevConfig { ... }

@Bean
@Profile({"dev", "test"})
public DataSource h2DataSource() { ... }

@Bean
@Profile("prod")
public DataSource prodDataSource() { ... }

Activate profiles:

# application.properties
spring.profiles.active=prod

# Command line
java -jar app.jar --spring.profiles.active=prod

# Environment variable
SPRING_PROFILES_ACTIVE=prod

# JVM property
-Dspring.profiles.active=prod

# Programmatic
SpringApplication app = new SpringApplication(MyApp.class);
app.setAdditionalProfiles("prod");

Profile-specific properties: application-prod.yml, application-dev.yml.

273 . Externalized configuration? ADVANCED

Spring Boot loads properties from many sources in priority order (later overrides earlier):

  1. Default properties (SpringApplication.setDefaultProperties)
  2. @PropertySource annotations
  3. application.properties / application.yml
  4. Profile-specific: application-{profile}.properties
  5. OS environment variables
  6. Java system properties (-D)
  7. Command line arguments (--property=value) ← highest priority
// Injecting properties
@Value("${app.name:MyApp}") // with default "MyApp"
private String appName;

// Typed configuration
@ConfigurationProperties(prefix = "app.mail")
@Validated
public class MailProperties {
    @NotBlank private String host;
    private int port = 25;
    private boolean ssl;
    // getters/setters
}

@SpringBootApplication
@EnableConfigurationProperties(MailProperties.class)
public class App { }

Spring Boot 2.4+: Config data API, spring.config.import to import from other files/vaults.

274 . application.yml vs properties? MID

Both configure the same properties:

# application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
server.port=8080
# application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: root
server:
  port: 8080

YAML advantages:

  • Less repetition of common prefixes
  • Supports lists and maps more naturally
  • Multiple profiles in one file using --- document separator
  • More readable for nested config
# application.yml with multiple profiles
spring:
  profiles:
    active: dev

spring:
  config:
    activate:
      on-profile: dev
  datasource:
    url: jdbc:h2:mem:testdb

spring:
  config:
    activate:
      on-profile: prod
  datasource:
    url: jdbc:mysql://prod-server:3306/mydb

YAML caveat: YAML is sensitive to indentation. Properties is simpler and less error-prone.

275 . Actuator? ADVANCED

Spring Boot Actuator provides production-ready endpoints for monitoring and management:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Key endpoints (/actuator/{id}):

  • /health: Application health (UP/DOWN + component details)
  • /info: Application info (build, git commit, etc.)
  • /metrics: Micrometer metrics (JVM, HTTP, custom)
  • /prometheus: Prometheus-format metrics
  • /env: Environment properties (sanitized)
  • /loggers: View/change log levels at runtime
  • /threaddump: Current thread dump
  • /heapdump: Download heap dump
  • /beans: All Spring beans
  • /mappings: All URL mappings
  • /auditevents: Security audit events
  • /refresh: (Cloud) Reload config
# Expose all endpoints (restrict in production!)
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always
276 . Health checks? ADVANCED

/actuator/health aggregates health indicators:

Built-in indicators: DB, Redis, Kafka, Rabbit, Disk space, Elasticsearch, Mail, etc.

// Custom health indicator
@Component
public class ExternalApiHealthIndicator implements HealthIndicator {
    
    @Override
    public Health health() {
        try {
            ResponseEntity<String> response = restTemplate.getForEntity(apiUrl, String.class);
            if (response.getStatusCode().is2xxSuccessful()) {
                return Health.up()
                    .withDetail("url", apiUrl)
                    .withDetail("responseTime", responseTime)
                    .build();
            }
            return Health.down().withDetail("status", response.getStatusCode()).build();
        } catch (Exception e) {
            return Health.down(e).build();
        }
    }
}

Kubernetes integration:

management:
  endpoint:
    health:
      probes:
        enabled: true  # enables /actuator/health/liveness and /actuator/health/readiness
277 . Spring Boot logging? ADVANCED

Default: Logback with SLF4J facade.

// SLF4J (facade - don't import Logback directly)
private static final Logger log = LoggerFactory.getLogger(UserService.class);
// Or with Lombok:
@Slf4j
public class UserService { }

log.debug("Processing user {}", userId);
log.info("User {} created", userId);
log.warn("Slow DB query: {}ms", elapsed);
log.error("Failed to process user {}", userId, exception);

Configuration in application.yml:

logging:
  level:
    root: INFO
    com.example: DEBUG
    org.hibernate.SQL: DEBUG       # log SQL
    org.hibernate.type: TRACE      # log SQL parameters
  file:
    name: /var/log/myapp.log
  pattern:
    console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"

Custom logback-spring.xml for advanced configuration (rolling files, async appenders, MDC).

278 . Logback vs Log4j2? MID
LogbackLog4j2
Default in Spring BootYesNo (exclude and add)
PerformanceGoodBetter (async appenders)
Async appendersAsyncAppenderAsyncLogger (LMAX Disruptor)
JSON loggingVia logstash-logback-encoderBuilt-in
Configurationlogback-spring.xmllog4j2-spring.xml
Hot reloadYesYes
SecurityLog4Shell vulnerability (2021) in Log4j2 2.xCVE-2021-44228 (fixed in 2.17+)

Switch to Log4j2:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

For high-throughput services: Log4j2 async loggers are significantly faster.

279 . Exception handling in REST? ADVANCED

Option 1: @RestControllerAdvice (recommended):

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ProblemDetail handleNotFound(ResourceNotFoundException ex, HttpServletRequest req) {
        ProblemDetail pd = ProblemDetail.forStatus(HttpStatus.NOT_FOUND);
        pd.setTitle("Resource Not Found");
        pd.setDetail(ex.getMessage());
        pd.setInstance(URI.create(req.getRequestURI()));
        return pd;
    }
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
        ProblemDetail pd = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
        pd.setTitle("Validation Failed");
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(e -> 
            errors.put(e.getField(), e.getDefaultMessage()));
        pd.setProperty("errors", errors);
        return pd;
    }
}

ProblemDetail (RFC 7807, Spring 6 / Spring Boot 3): Standard error response format.

Option 2: ResponseEntityExceptionHandler: Extend for default Spring MVC exception handling.

280 . @ControllerAdvice? ADVANCED

@ControllerAdvice applies cross-cutting concerns to multiple controllers:

  1. Exception handling: @ExceptionHandler methods
  2. Model attributes: @ModelAttribute methods (adds to all model maps)
  3. Data binding: @InitBinder methods (customize data binding)

@RestControllerAdvice = @ControllerAdvice + @ResponseBody

Scoping:

@ControllerAdvice(assignableTypes = {UserController.class, OrderController.class})
@ControllerAdvice(basePackages = "com.example.api")
@ControllerAdvice(annotations = RestController.class)

Order: Multiple @ControllerAdvice can coexist; use @Order to prioritize.

281 . Validation mechanism? ADVANCED

Spring Boot uses Bean Validation (JSR 380 / Jakarta Validation) via Hibernate Validator:

// DTO with constraints
public record CreateUserRequest(
    @NotBlank(message = "Name is required")
    @Size(min = 2, max = 100)
    String name,
    
    @Email
    @NotNull
    String email,
    
    @Min(18) @Max(120)
    int age,
    
    @Pattern(regexp = "^\\+?[1-9]\\d{1,14}$")
    String phone
) {}

// Controller - @Valid triggers validation
@PostMapping("/users")
public UserDTO createUser(@Valid @RequestBody CreateUserRequest request) {
    return userService.create(request);
}

// Service-level validation
@Service
@Validated
public class UserService {
    public void updateEmail(@Valid @Email String email, Long userId) { ... }
}

Groups: Validate different constraints in different scenarios (@Validated(CreateGroup.class)).

Custom validator:

@Target(FIELD) @Retention(RUNTIME)
@Constraint(validatedBy = UniqueEmailValidator.class)
public @interface UniqueEmail {
    String message() default "Email already exists";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
282 . Spring Data JPA basics? ADVANCED

Spring Data JPA eliminates boilerplate DAO code:

// Repository - just declare the interface
public interface UserRepository extends JpaRepository<User, Long> {
    
    // Derived query from method name
    List<User> findByEmailAndActive(String email, boolean active);
    
    // Custom JPQL
    @Query("SELECT u FROM User u WHERE u.age > :minAge ORDER BY u.name")
    List<User> findUsersOlderThan(@Param("minAge") int minAge);
    
    // Native SQL
    @Query(value = "SELECT * FROM users WHERE region = ?1", nativeQuery = true)
    List<User> findByRegionNative(String region);
    
    // Pagination
    Page<User> findByDepartment(String dept, Pageable pageable);
    
    // Projection
    List<UserNameEmail> findAllProjectedBy();
    
    // Modifying
    @Modifying
    @Transactional
    @Query("UPDATE User u SET u.active = false WHERE u.lastLogin < :cutoff")
    int deactivateOldUsers(@Param("cutoff") LocalDateTime cutoff);
}

JpaRepository provides: save(), findById(), findAll(), delete(), count(), existsById(), pagination, sorting.

283 . Repository patterns? ADVANCED

Spring Data offers multiple repository interfaces:

  • CrudRepository<T, ID>: Basic CRUD
  • PagingAndSortingRepository<T, ID>: + pagination/sorting
  • JpaRepository<T, ID>: + JPA-specific (flush, batch)

Custom base repository:

@NoRepositoryBean
public interface BaseRepository<T, ID> extends JpaRepository<T, ID> {
    List<T> findByCreatedAfter(LocalDateTime date);
}

Specification pattern (for dynamic queries):

public class UserSpecifications {
    public static Specification<User> hasEmail(String email) {
        return (root, query, cb) -> cb.equal(root.get("email"), email);
    }
    
    public static Specification<User> isActive() {
        return (root, query, cb) -> cb.isTrue(root.get("active"));
    }
}

// Use:
userRepository.findAll(
    UserSpecifications.hasEmail(email).and(UserSpecifications.isActive())
);
284 . Transaction management? ADVANCED

Spring's @Transactional declarative transaction management:

@Service
@Transactional(readOnly = true) // default for all methods
public class OrderService {
    
    @Transactional // overrides class default - read-write
    public Order createOrder(CreateOrderRequest request) {
        Order order = new Order(request);
        order = orderRepository.save(order);
        inventoryService.reserve(order); // participates in same transaction
        return order;
    }
    
    @Transactional(
        propagation = Propagation.REQUIRES_NEW, // new transaction always
        isolation = Isolation.SERIALIZABLE,
        timeout = 30, // seconds
        rollbackFor = Exception.class, // rollback on all exceptions
        noRollbackFor = InformationalException.class
    )
    public void auditOrder(Long orderId) { ... }
}

Propagation types:

  • REQUIRED (default): Join existing or create new
  • REQUIRES_NEW: Always create new, suspend existing
  • NESTED: Save point within existing transaction
  • SUPPORTS: Join if exists, non-transactional if not
  • MANDATORY: Must have existing transaction
  • NEVER: Must NOT have existing transaction
  • NOT_SUPPORTED: Suspend existing, run non-transactionally
285 . @Transactional pitfalls? ADVANCED
  1. Self-invocation: Calling a @Transactional method from within the same class bypasses the proxy:
@Service
public class OrderService {
    public void process() {
        createOrder(); // NO transaction! Self-call bypasses proxy
    }
    
    @Transactional
    public void createOrder() { ... }
}
// Fix: Inject self, or move to separate class, or use AspectJ mode
  1. Non-public methods: @Transactional on private/protected/package methods is ignored (proxy limitation).
  1. Checked exceptions don't rollback by default: Only RuntimeException and Error. Use rollbackFor = Exception.class.
  1. Catching exception inside transaction: If you catch and swallow the exception, no rollback:
@Transactional
void method() {
    try { riskyOperation(); }
    catch (Exception e) { log.error(e); } // COMMIT happens! No rollback
}
  1. readOnly misuse: readOnly = true is a hint to optimize; doesn't prevent writes. Use it for query-only methods.
  1. Propagation.REQUIRES_NEW + exception handling: Exception in outer transaction doesn't rollback inner, and vice versa.
286 . Lazy vs eager loading? MID

Eager loading: Fetch associated entities immediately when parent is loaded.

Lazy loading: Fetch associated entities only when accessed.

@Entity
public class Order {
    @ManyToOne(fetch = FetchType.EAGER) // always loaded
    private Customer customer;
    
    @OneToMany(fetch = FetchType.LAZY) // loaded on access
    private List<OrderItem> items;
}

JPA defaults: @OneToMany and @ManyToMany default to LAZY. @ManyToOne and @OneToOne default to EAGER.

LazyInitializationException: Accessing a lazy collection outside a transaction (session is closed):

Order order = orderRepository.findById(id).get();
// Transaction closed here if method wasn't @Transactional
order.getItems().size(); // LazyInitializationException!

Fixes:

  • @Transactional on the service method
  • JOIN FETCH in JPQL: SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id
  • @EntityGraph
  • Projections
287 . N+1 problem? ADVANCED

Classic N+1: Load N entities, then execute 1 query per entity to load associations = N+1 queries total.

// N+1 problem
List<Order> orders = orderRepository.findAll(); // 1 query
for (Order o : orders) {
    System.out.println(o.getCustomer().getName()); // N queries!
}
// Total: 1 + N queries

Solutions:

  1. JOIN FETCH:
@Query("SELECT o FROM Order o JOIN FETCH o.customer WHERE o.status = :status")
List<Order> findByStatusWithCustomer(@Param("status") String status);
  1. @EntityGraph:
@EntityGraph(attributePaths = {"customer", "items"})
List<Order> findByStatus(String status);
  1. Batch fetching: @BatchSize(size = 50) on association – fetches 50 at a time instead of 1.
  1. Projection/DTO: Fetch only needed data.
  1. Enable spring.jpa.properties.hibernate.default_batch_fetch_size=50: Global batch fetching.

Detect N+1: Enable SQL logging, use datasource-proxy, Hibernate's statistics, tools like p6spy.

288 . Paging and sorting? ADVANCED
// Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Page<User> findByDepartment(String dept, Pageable pageable);
    Slice<User> findByActive(boolean active, Pageable pageable); // no count query
}

// Service
Pageable pageable = PageRequest.of(
    pageNumber,      // 0-based
    pageSize,
    Sort.by("name").ascending().and(Sort.by("age").descending())
);

Page<User> page = userRepository.findByDepartment("Engineering", pageable);
page.getContent();       // List<User> for current page
page.getTotalElements(); // total count (runs COUNT query)
page.getTotalPages();
page.getNumber();        // current page
page.hasNext();

// REST controller
@GetMapping("/users")
public Page<UserDTO> getUsers(
    @RequestParam(defaultValue = "0") int page,
    @RequestParam(defaultValue = "20") int size,
    @RequestParam(defaultValue = "name") String sort) {
    
    Pageable pageable = PageRequest.of(page, size, Sort.by(sort));
    return userRepository.findAll(pageable).map(userMapper::toDto);
}

Slice vs Page: Slice doesn't execute COUNT query (better performance when total count not needed – e.g., infinite scroll).

289 . REST best practices? ADVANCED
  1. Use proper HTTP methods: GET (read), POST (create), PUT (full update), PATCH (partial), DELETE
  2. Meaningful URIs: /users/{id}/orders not /getUserOrders
  3. Plural nouns: /users not /user
  4. HTTP status codes: 200 OK, 201 Created, 204 No Content, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 422 Unprocessable, 500 Internal Error
  5. Versioning: /api/v1/users or Accept: application/vnd.myapp.v1+json
  6. Pagination: Consistent page/size parameters, include metadata
  7. Filtering/sorting: Query params: /users?status=active&sort=name,asc
  8. Consistent error responses: Problem Details (RFC 7807)
  9. Idempotency: PUT, DELETE should be idempotent
  10. HATEOAS: Link to related resources (see Q290)
  11. Security: Always validate input, use HTTPS, authentication on all endpoints
  12. Documentation: OpenAPI/Swagger
  13. Content negotiation: Support application/json; potentially XML if required
290 . HATEOAS? ADVANCED

HATEOAS (Hypermedia As The Engine Of Application State): REST responses include links to related actions/resources.

{
  "id": 123,
  "name": "Alice",
  "email": "alice@example.com",
  "_links": {
    "self": { "href": "/api/users/123" },
    "orders": { "href": "/api/users/123/orders" },
    "update": { "href": "/api/users/123", "method": "PUT" },
    "delete": { "href": "/api/users/123", "method": "DELETE" }
  }
}

Spring HATEOAS:

@GetMapping("/users/{id}")
public EntityModel<UserDTO> getUser(@PathVariable Long id) {
    UserDTO user = userService.findById(id);
    return EntityModel.of(user,
        linkTo(methodOn(UserController.class).getUser(id)).withSelfRel(),
        linkTo(methodOn(OrderController.class).getUserOrders(id)).withRel("orders")
    );
}

In practice: Full HATEOAS is rarely implemented in enterprise APIs. Self links and related resource links are the most common usage.

291 . Security basics? ADVANCED

Spring Security added via spring-boot-starter-security. Auto-configures:

  • HTTP Basic auth
  • Default in-memory user
  • CSRF protection
  • Secure all endpoints

Custom configuration (Spring Boot 3 / Spring Security 6):

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable()) // for REST APIs
            .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers(HttpMethod.GET, "/api/**").hasAnyRole("USER", "ADMIN")
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12); // strength 12
    }
}
292 . JWT authentication? ADVANCED
// JWT Filter
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain chain) throws IOException, ServletException {
        String authHeader = request.getHeader("Authorization");
        
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            return;
        }
        
        String token = authHeader.substring(7);
        String username = jwtService.extractUsername(token);
        
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            
            if (jwtService.isTokenValid(token, userDetails)) {
                UsernamePasswordAuthenticationToken authToken = 
                    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }
        chain.doFilter(request, response);
    }
}

// JWT Service
@Service
public class JwtService {
    @Value("${jwt.secret}") private String secret;
    @Value("${jwt.expiration:3600}") private long expiration;
    
    public String generateToken(UserDetails userDetails) {
        return Jwts.builder()
            .subject(userDetails.getUsername())
            .issuedAt(new Date())
            .expiration(new Date(System.currentTimeMillis() + expiration * 1000))
            .claim("roles", userDetails.getAuthorities())
            .signWith(getSigningKey())
            .compact();
    }
}

Key security considerations:

  • Store JWT secret securely (Vault, Secrets Manager)
  • Short expiration + refresh token pattern
  • Token invalidation/blacklist for logout
  • Validate issuer, audience, not-before claims
  • Use RS256 (asymmetric) over HS256 for microservices
293 . OAuth2 with Spring? ADVANCED

Spring Security OAuth2 (Spring Boot 3):

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${GOOGLE_CLIENT_ID}
            client-secret: ${GOOGLE_CLIENT_SECRET}
            scope: openid, profile, email
      resourceserver:
        jwt:
          issuer-uri: https://accounts.google.com

Resource Server (validate JWT from auth server):

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
            );
        return http.build();
    }
    
    @Bean
    JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
        converter.setAuthoritiesClaimName("roles");
        converter.setAuthorityPrefix("ROLE_");
        JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
        jwtConverter.setJwtGrantedAuthoritiesConverter(converter);
        return jwtConverter;
    }
}

Grant types: Authorization Code (web), Client Credentials (machine-to-machine), Device Flow (IoT).

294 . Caching in Spring? ADVANCED
// Enable caching
@SpringBootApplication
@EnableCaching
public class App { }

// Use caching
@Service
public class ProductService {
    
    @Cacheable(value = "products", key = "#id", unless = "#result == null")
    public Product getProduct(Long id) {
        return repository.findById(id).orElse(null); // DB call only on cache miss
    }
    
    @CachePut(value = "products", key = "#product.id") // always execute + update cache
    public Product updateProduct(Product product) {
        return repository.save(product);
    }
    
    @CacheEvict(value = "products", key = "#id") // remove from cache
    public void deleteProduct(Long id) {
        repository.deleteById(id);
    }
    
    @CacheEvict(value = "products", allEntries = true) // clear entire cache
    @Scheduled(fixedRate = 3600000) // hourly
    public void evictAllProducts() { }
    
    @Caching(evict = {
        @CacheEvict("products"),
        @CacheEvict(value = "productsByCategory", key = "#product.category")
    })
    public void processProduct(Product product) { }
}

Default cache: ConcurrentMapCacheManager (in-memory, no TTL). Configure TTL with Caffeine, Redis, etc.

295 . Redis integration? ADVANCED
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
  data:
    redis:
      host: localhost
      port: 6379
      password: ${REDIS_PASSWORD}
      lettuce:
        pool:
          max-active: 10

Using as cache:

@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(30))
        .serializeValuesWith(RedisSerializationContext.SerializationPair
            .fromSerializer(new GenericJackson2JsonRedisSerializer()));
    
    return RedisCacheManager.builder(connectionFactory)
        .cacheDefaults(config)
        .withCacheConfiguration("products", 
            RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1)))
        .build();
}

Using RedisTemplate directly:

@Autowired RedisTemplate<String, Object> redisTemplate;

redisTemplate.opsForValue().set("key", value, Duration.ofMinutes(30));
Object value = redisTemplate.opsForValue().get("key");
redisTemplate.opsForHash().put("hash", "field", "value");
redisTemplate.opsForList().leftPush("list", "element");

Session storage:

<dependency>spring-session-data-redis</dependency>
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
296 . Microservices with Spring Boot? ADVANCED

Spring Boot is a natural fit for microservices:

Principles:

  • Single responsibility: one service per bounded context
  • Independent deployability
  • Own data store per service
  • Communicate via HTTP/REST or messaging (Kafka, RabbitMQ)

Common patterns:

// Service discovery (Eureka)
@EnableEurekaClient

// Load balancing
@LoadBalanced
@Bean RestTemplate restTemplate() { return new RestTemplate(); }

// Feign client (declarative HTTP)
@FeignClient(name = "order-service")
public interface OrderClient {
    @GetMapping("/orders/{id}")
    OrderDTO getOrder(@PathVariable Long id);
}

// Config server
spring.config.import=configserver:http://config-server:8888

// Distributed tracing (Micrometer Tracing + Zipkin)
management.tracing.sampling.probability=1.0

Challenges: Network latency, eventual consistency, distributed transactions (Saga pattern), service discovery, centralized logging, distributed tracing.

297 . Spring Cloud overview? ADVANCED

Spring Cloud provides tools for distributed systems:

ComponentPurpose
Spring Cloud ConfigCentralized config server
Netflix EurekaService registry/discovery
Spring Cloud GatewayAPI gateway, routing, rate limiting
OpenFeignDeclarative REST client
Resilience4jCircuit breaker, retry, rate limiter
Spring Cloud SleuthDistributed tracing (replaced by Micrometer Tracing)
ZipkinTrace visualization
Spring Cloud BusPropagate config changes via message broker
Spring Cloud StreamMessage-driven microservices (Kafka/RabbitMQ)
298 . Circuit breaker? EXPERT

Circuit breaker prevents cascading failures when a dependency is down:

States:

  • CLOSED: Normal operation; requests pass through
  • OPEN: Too many failures; requests fail fast (no call to dependency)
  • HALF-OPEN: After wait period, allow limited requests to test recovery
// Resilience4j
@CircuitBreaker(name = "orderService", fallbackMethod = "fallbackOrder")
@Retry(name = "orderService")
@TimeLimiter(name = "orderService")
public CompletableFuture<Order> getOrder(Long id) {
    return CompletableFuture.supplyAsync(() -> orderClient.getOrder(id));
}

public CompletableFuture<Order> fallbackOrder(Long id, Exception ex) {
    log.warn("Circuit open for order {}: {}", id, ex.getMessage());
    return CompletableFuture.completedFuture(Order.empty(id));
}
resilience4j:
  circuitbreaker:
    instances:
      orderService:
        slidingWindowSize: 10
        failureRateThreshold: 50
        waitDurationInOpenState: 30s
        permittedNumberOfCallsInHalfOpenState: 3
299 . Resilience4j? ADVANCED

Resilience4j provides multiple resilience patterns:

  1. Circuit Breaker: See Q298
  2. Retry:
@Retry(name = "externalApi", fallbackMethod = "fallback")
public String callApi() { ... }
resilience4j.retry.instances.externalApi:
  maxAttempts: 3
  waitDuration: 500ms
  enableExponentialBackoff: true
  exponentialBackoffMultiplier: 2
  1. Rate Limiter:
@RateLimiter(name = "apiGateway")
public String callGateway() { ... }
  1. Bulkhead: Limit concurrent calls (Thread pool or Semaphore)
@Bulkhead(name = "service", type = Bulkhead.Type.SEMAPHORE)
  1. Time Limiter: Timeout for async calls
  1. Cache: Simple result caching

Combining:

@CircuitBreaker(name = "svc", fallbackMethod = "fb")
@Retry(name = "svc")
@RateLimiter(name = "svc")
public Result callService() { ... }
// Order: RateLimiter → CircuitBreaker → Retry
300 . Production readiness checklist? ADVANCED

A Spring Boot service is production-ready when it has:

Observability:

  • [ ] Structured logging with correlation IDs (MDC)
  • [ ] Metrics exposed (/actuator/prometheus or micrometer)
  • [ ] Distributed tracing (Micrometer Tracing + Zipkin/Jaeger)
  • [ ] Health checks for orchestration (/actuator/health/liveness, readiness)
  • [ ] Alerting on error rate, latency, saturation

Resilience:

  • [ ] Circuit breakers for external dependencies
  • [ ] Retry with backoff
  • [ ] Timeouts on all external calls (HTTP client, DB, etc.)
  • [ ] Bulkheads (bounded thread pools)
  • [ ] Graceful shutdown (spring.lifecycle.timeout-per-shutdown-phase=30s)

Security:

  • [ ] Authentication + authorization
  • [ ] Secrets in vault/environment, not in code
  • [ ] HTTPS enforced
  • [ ] Input validation
  • [ ] Dependency vulnerability scan (OWASP Dependency Check, Snyk)
  • [ ] Actuator endpoints secured or restricted

Performance:

  • [ ] Connection pool tuned (HikariCP: maximum-pool-size)
  • [ ] Database indexes for common queries
  • [ ] Caching for expensive/repeated operations
  • [ ] Thread pool sized appropriately
  • [ ] JVM heap and GC tuned
  • [ ] Load test results acceptable

Deployment:

  • [ ] Dockerfile with non-root user, minimal base image
  • [ ] Kubernetes liveness/readiness probes configured
  • [ ] Resource requests/limits set
  • [ ] Rolling update strategy
  • [ ] -XX:+UseContainerSupport for JVM container awareness
  • [ ] Environment-specific configuration externalized

Testing:

  • [ ] Unit tests (target >80% coverage on business logic)
  • [ ] Integration tests (Testcontainers for real DB)
  • [ ] Contract tests (Pact or Spring Cloud Contract)
  • [ ] Load test results meet SLA

Documentation:

  • [ ] OpenAPI/Swagger (springdoc-openapi)
  • [ ] README with runbook
  • [ ] Architecture decision records (ADRs)

Ops:

  • [ ] Log rotation configured
  • [ ] Heap dump path set (-XX:HeapDumpPath)
  • [ ] OOM action: -XX:+ExitOnOutOfMemoryError
  • [ ] JFR enabled for profiling capability
  • [ ] Runbook for common failure scenarios