GAP·MAP

Spring & Spring Boot

[ middle depth ]

Why the default bean is a shared singleton

Every Spring bean is a singleton unless you say otherwise. Singleton here means one instance per application context, cached and shared by every collaborator, not one instance per JVM. A mutable field on a @Service is therefore shared state across all request threads. Keep singletons stateless, and put per-request data on a local variable or a request-scoped bean.

Prototype scope hands out a new instance on every injection or getBean() call. One catch: Spring initializes a prototype (@PostConstruct runs) and then forgets it. @PreDestroy and destroy callbacks never fire for prototypes, so if the bean holds a connection or a file handle, you close it yourself.

Why @Transactional does nothing when you call it from the same class

The advice lives on a proxy that wraps your bean. A caller holding the bean reference reaches the proxy, the transaction interceptor runs, then your method runs. Once you are inside the object, this.other() calls the raw method and skips the proxy.

Wrong "recordAudit is @Transactional, so it runs in a transaction no matter who calls it." When a non-transactional method in the same class calls this.recordAudit(), the annotation is ignored and no transaction starts. Right move the method to another bean and call it across that boundary, so the call travels through the proxy. The same rule disables @Async and @Cacheable on internal calls. This is the single most common Spring bug interviewers probe.

When @Transactional rolls back, and when it quietly commits

By default a transaction rolls back on RuntimeException and Error, and commits on checked exceptions. A method that writes a row and throws IOException leaves that row committed, which surprises anyone expecting any exception to undo the work. To roll back on a checked type, declare @Transactional(rollbackFor = Exception.class). readOnly defaults to false. A proxy can only advise an overridable method, so a private method never gets a transaction. JDK interface proxies require the method to be public on the interface; CGLIB class-based proxies (Spring Boot's default) also advise protected and package-visible methods as of Spring 6.0.

What @WebMvcTest and @DataJpaTest load and skip

A slice loads part of the context, not all of it. @WebMvcTest wires the web layer (@Controller, @ControllerAdvice, filters) and gives you MockMvc with no running server. It does not scan @Service or @Repository, so supply collaborators with @MockitoBean (the replacement for the removed @MockBean) or @Import. @DataJpaTest swaps your DataSource for an in-memory database by default and rolls back after each test, so vendor-specific SQL can pass on H2 and still fail on Postgres. Point it at the real database with @AutoConfigureTestDatabase(replace = Replace.NONE).

[ senior depth ]

How Spring wraps a bean in a proxy

Bean creation runs in order: instantiate, inject dependencies, call Aware setters, run BeanPostProcessor.postProcessBeforeInitialization, fire @PostConstruct, then afterPropertiesSet(), then the init-method, then postProcessAfterInitialization. That last step is where Spring's AOP post-processor returns a proxy in place of your bean. Two consequences follow. Init callbacks run on the raw, unproxied instance, so calling a @Transactional method from @PostConstruct does nothing because the proxy is not in place yet. And the object handed to collaborators is the proxy, while this inside the target is the raw bean, which is why self-invocation skips advice.

The proxy is a JDK dynamic proxy when the bean implements an interface, or a CGLIB subclass when it does not. Spring Boot forces CGLIB by default (spring.aop.proxy-target-class is true since Boot 2.0), so beans get subclass proxies even behind an interface. Because CGLIB subclasses the target, it cannot proxy a final class, and final or private methods silently escape advice, since a subclass cannot override them.

REQUIRED vs REQUIRES_NEW and UnexpectedRollbackException

REQUIRED, the default, joins an existing transaction or starts one. Several REQUIRED scopes collapse into one physical transaction on one connection. REQUIRES_NEW suspends the outer transaction and opens an independent one on a second connection, so the inner commit or rollback stands alone.

Wrong "an inner REQUIRED method that catches its own exception protects the outer transaction." The inner scope marks the shared physical transaction rollback-only, so the outer commit throws UnexpectedRollbackException. Right use REQUIRES_NEW when the inner work must survive an outer failure, such as an audit trail, accepting that it holds a second connection while the outer is suspended, so nesting it in a loop can drain the connection pool.

The DispatcherServlet request path

One servlet fronts every request. DispatcherServlet asks a HandlerMapping for the handler, a HandlerAdapter invokes it (running the interceptors' preHandle and postHandle around it), the controller returns a model plus view name or writes the body itself, and a ViewResolver turns the name into a View that renders. @ResponseBody and ResponseEntity write the response inside the adapter, so no view resolution runs. Exceptions route to HandlerExceptionResolver beans such as @ControllerAdvice.

How auto-configuration backs off

Auto-configuration classes are listed in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, never component-scanned, and the old spring.factories key was dropped in Boot 3.0. Each class carries conditions: @ConditionalOnClass gates on a starter being present, and @ConditionalOnMissingBean is the back-off. Auto-config runs after your own beans, so defining your own DataSource makes the default embedded one back off and your bean wins. Boot 4 split auto-config into per-technology modules, so a driver on the classpath without its matching auto-config module leaves that configuration silently absent.

beta

The interviewer part is in the works.

The diagnostic, personal maps, and AI mock interviews are being finished right now. The notes stay free either way. Leave an email and you'll get the first-cohort invite, plus a month of Pro when it opens.

builds on

more Java

was this useful?