Skip to content

Spring Boot

Core container

What is IoC and dependency injection? Why use it?

Inversion of control: the framework creates and wires objects instead of your code calling new. Dependency injection is how: a bean declares what it needs and the container supplies it. Benefits: loose coupling, easy testing (inject mocks), configuration in one place, cross-cutting features via proxies.

Constructor vs. field vs. setter injection?

Constructor injection is preferred: dependencies are explicit and final, the object is never half-built, and tests can call the constructor without Spring. With one constructor, @Autowired is optional. Field injection hides dependencies and needs reflection to test. Setter injection only for optional dependencies.

What are bean scopes?

singleton (default, one per container), prototype (new each time it's requested), and in web apps request, session, application, websocket. Injecting a prototype into a singleton gives you one instance forever; use ObjectProvider<T> or a scoped proxy to get a fresh one.

Describe the bean lifecycle.

Instantiate → inject dependencies → *Aware callbacks → BeanPostProcessor.postProcessBeforeInitialization → @PostConstruct / afterPropertiesSet / init-method → postProcessAfterInitialization (where AOP proxies are created) → in use → @PreDestroy / destroy on shutdown.

@Component vs. @Service vs. @Repository vs. @Controller? @Bean vs. @Component?

All are @Component stereotypes found by component scanning. @Repository also translates persistence exceptions into DataAccessException; @Controller/@RestController are web handlers. @Bean on a method in a @Configuration class registers a bean you construct yourself, typically for third-party classes you can't annotate.

Two beans of the same type: how does Spring choose?

It fails with NoUniqueBeanDefinitionException unless you use @Primary on one, @Qualifier("name") at the injection point, or inject a List<T>/Map<String, T> of all of them (handy for strategy patterns).

How do you handle circular dependencies?

Spring Boot 2.6+ forbids them by default. Fix the design: extract the shared part into a third bean, use events, or inject lazily (@Lazy, ObjectProvider). A circular dependency is usually a sign of mixed responsibilities.

Spring Boot specifically

How does auto-configuration work?

@SpringBootApplication = @Configuration + @EnableAutoConfiguration + @ComponentScan. Auto-configuration classes are listed in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (older: spring.factories) and guarded by conditions: @ConditionalOnClass (library on the classpath), @ConditionalOnMissingBean (you didn't define your own), @ConditionalOnProperty. So adding spring-boot-starter- data-redis gives you a RedisTemplate unless you define one. Debug with --debug (conditions report) or the /actuator/conditions endpoint.

What is a starter?

A dependency bundle with sensible versions plus auto-configuration for a feature: spring-boot-starter-web, -data-jpa, -security, -actuator, -test.

How do profiles and externalised configuration work?

Properties come from many sources with an order of precedence (command-line args > env vars > profile-specific files > application.yml > defaults). spring.profiles.active=prod loads application-prod.yml. Bind groups of properties to a typed, validated class with @ConfigurationProperties. Secrets come from env vars or a secret store (AWS Secrets Manager, Vault), never committed.

What does Actuator give you?

Production endpoints: /actuator/health (liveness/readiness for Kubernetes probes), /metrics, /prometheus (Micrometer), /info, /env, /loggers (change log levels at runtime), thread dump, heap dump. Secure or limit them in production.

Prayog link: Micrometer metrics scraped by Prometheus, shown in Grafana from gateway to ledger.

Web and REST

What happens when an HTTP request hits a Spring MVC app?

Tomcat thread → filters (security, logging) → DispatcherServlet → HandlerMapping finds the controller method → interceptors → argument resolvers (@PathVariable, @RequestBody via Jackson HttpMessageConverter, validation) → controller → return value converted to JSON → response. Exceptions go to @ExceptionHandler / @ControllerAdvice.

How do you do global exception handling?

A @RestControllerAdvice with @ExceptionHandler methods that map exceptions to status codes and a consistent error body (Spring 6 supports RFC 7807 ProblemDetail). Don't leak stack traces; log with a correlation id.

What makes an API RESTful? Which methods are idempotent?

Resources as nouns (/models/{id}), HTTP methods as verbs, proper status codes, stateless requests, representations (JSON). Idempotent: GET, PUT, DELETE, HEAD, OPTIONS (repeating has the same effect). Not: POST, PATCH (generally). Make POST safe to retry with an idempotency key.

Prayog link: client order IDs act as idempotency keys: a retried order is rejected as DUPLICATE_CLIENT_ORDER_ID instead of trading twice.

Status codes you should know?

200 OK, 201 Created, 202 Accepted, 204 No Content; 400 Bad Request, 401 Unauthorized (not authenticated), 403 Forbidden (authenticated, not allowed), 404, 409 Conflict, 422 Unprocessable, 429 Too Many Requests; 500, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout.

How do you version an API?

URI (/v1/..., simplest, what Prayog uses: /api/v1/...), header (Accept: application/vnd.x.v2+json), or query param. Prefer additive, backward-compatible changes so you rarely need a new version.

Spring MVC vs. WebFlux?

MVC: thread per request, blocking, simple to reason about and debug. WebFlux: non-blocking on Netty with Reactor (Mono/Flux), handles many concurrent connections with few threads, good for streaming and WebSockets, but the whole call chain must be non-blocking and debugging is harder. With virtual threads (Java 21), MVC handles high concurrency too, which removes most reasons to go reactive for plain I/O.

Prayog link: the gateway uses WebFlux because responses are completed asynchronously after the journal's fsync, and it serves WebSocket market data.

How do you validate input?

Bean Validation: @Valid @RequestBody OrderRequest req with @NotNull, @Positive, @Size on fields; custom constraints for business rules; handle MethodArgumentNotValidException in the advice for a 400 with field errors.

Data and transactions

How does @Transactional work? Common pitfalls?

Spring wraps the bean in a proxy; the proxy starts a transaction before the method and commits or rolls back after. Pitfalls:

  1. Self-invocation: calling a @Transactional method from another method in the same class bypasses the proxy, so no transaction.
  2. Only public methods are proxied (by default).
  3. By default rollback only on unchecked exceptions; use rollbackFor = Exception.class for checked ones.
  4. Catching the exception inside the method means no rollback.
  5. Long transactions holding locks while calling remote services.
Explain propagation and isolation.

Propagation: REQUIRED (default: join or start), REQUIRES_NEW (suspend outer, start a new one: e.g. audit log that must commit even if the outer rolls back), NESTED (savepoint), SUPPORTS, MANDATORY, NEVER. Isolation: READ_UNCOMMITTED (dirty reads), READ_COMMITTED (default in Postgres/Oracle; no dirty reads), REPEATABLE_READ (default in MySQL; no non-repeatable reads), SERIALIZABLE (no phantoms; slowest).

What is the N+1 problem?

Loading a list of N entities, then lazily loading a relation for each one: 1 + N queries. Fix with a join fetch (JOIN FETCH, @EntityGraph), batch fetching, or a single query that returns what you need (MyBatis result maps with joins).

JPA/Hibernate vs. MyBatis vs. jOOQ vs. JDBC template?

JPA: entities and generated SQL; productive for CRUD, risky for complex queries (N+1, surprises). MyBatis: you write SQL, it maps results (what I used at work). jOOQ: type-safe SQL built in Java from the schema (what Prayog uses). JdbcTemplate: thin wrapper over JDBC. Pick by how much control over SQL you need.

Security

How does Spring Security work at a high level?

A chain of servlet filters (SecurityFilterChain). Authentication filters build an Authentication (from a session, Basic auth, or a JWT), store it in the SecurityContext; authorisation checks rules on URLs (requestMatchers(...).hasRole(...)) or methods (@PreAuthorize).

How does JWT/OAuth2 resource-server authentication work?

The client gets an access token from the identity provider (Keycloak, Okta, Cognito) via OAuth2/OIDC. The API validates each token offline: checks the signature against the provider's public keys (JWKS, cached), plus expiry, issuer and audience. No call to the IdP per request. Roles/scopes come from claims. Trade-off: a JWT can't be revoked before it expires, so keep access tokens short-lived.

Prayog link: gateway checks JWTs offline against Keycloak's JWKS; the rate-limit tier comes from the token.

What is CSRF and when do you need protection? What is CORS?

CSRF: a malicious site makes the browser send a request with your cookies. Needed for cookie-based sessions; not for APIs authenticated with a bearer token in a header. CORS: the browser blocks cross-origin requests unless the server allows the origin via Access-Control-Allow-Origin (and answers the preflight OPTIONS).

Testing

How do you test a Spring Boot app?
  • Unit tests: plain JUnit 5 + Mockito, no Spring context (fast).
  • Slices: @WebMvcTest (controller + MockMvc), @DataJpaTest, @JsonTest.
  • Integration: @SpringBootTest with Testcontainers for a real Postgres/Kafka/Redis.
  • Contract tests for APIs other teams call.

Prayog link: Testcontainers for Postgres and Kafka in the post-trade ITs, a paused Kafka broker in KafkaPublisherIT.

Microservices patterns

How do services communicate? Sync vs. async?

Sync (REST/gRPC): simple, immediate answer, but couples availability (if B is down, A fails). Async (Kafka, SQS): decouples in time, absorbs load, but eventual consistency and harder debugging. Use sync for queries the user waits on, async for events and work that can happen later.

Circuit breaker, retry, bulkhead, timeout?

Timeout: never wait forever. Retry with exponential backoff and jitter, only for idempotent operations. Circuit breaker: after N failures, fail fast for a while, then try again (half-open). Bulkhead: separate thread pools/limits per dependency so one slow dependency can't exhaust everything. Resilience4j provides all.

How do you keep data consistent across services (distributed transactions)?

Avoid two-phase commit. Use the Saga pattern (a sequence of local transactions with compensating actions, orchestrated or choreographed) and the transactional outbox (write the event to an outbox table in the same DB transaction as the change; a relay publishes it to Kafka) so the DB and the event never disagree. Consumers must be idempotent.

Prayog link: the Kafka publisher tails the journal, which is the same idea as an outbox: the durable record comes first, publishing follows from it.