Core Spring Boot 3.x annotations, bean scopes and lifecycle, auto-configuration, profiles, properties and REST/exception-handling recipes.
Stereotype and configuration annotations
| Annotation |
Purpose |
@Component |
generic bean found by component scan |
@Service / @Repository / @Controller |
specializations; @Repository adds persistence exception translation |
@RestController |
@Controller + @ResponseBody |
@Configuration |
class with @Bean methods; CGLIB-proxied so inter-bean calls return the singleton (proxyBeanMethods = false to skip) |
@Bean |
method return value registered as a bean (name = method name) |
@SpringBootApplication |
@Configuration + @EnableAutoConfiguration + @ComponentScan (of its package and below) |
@Import, @ImportResource |
pull in other configuration |
@Autowired |
injection point; optional on a single constructor (since Spring 4.3) |
@Qualifier("name") / @Primary |
disambiguate multiple candidates |
@Value("${key:default}") |
single property / SpEL (#{...}) |
@ConfigurationProperties(prefix = "app") |
type-safe binding (relaxed names: app.max-size -> maxSize) |
@Lazy |
defer creation until first use |
@DependsOn |
force init ordering |
@Conditional* |
OnProperty, OnClass, OnMissingBean, OnBean, OnWebApplication |
@Profile("prod") |
bean only when profile active |
- Prefer constructor injection (final fields, testable, fails fast on missing bean); field injection hides dependencies.
- Circular constructor dependencies fail; Spring Boot 2.6+ forbids circular references by default (
spring.main.allow-circular-references=true to relax).
@ConfigurationProperties + records (constructor binding) works without @ConstructorBinding for single-constructor classes in Boot 3.
Web annotations
| Annotation |
Notes |
@RequestMapping, @GetMapping, @PostMapping, @PutMapping, @PatchMapping, @DeleteMapping |
path, produces, consumes |
@PathVariable |
/users/{id} |
@RequestParam |
query/form param; required=true default |
@RequestBody |
JSON -> object via HttpMessageConverter (Jackson) |
@RequestHeader, @CookieValue |
header / cookie |
@ResponseStatus(HttpStatus.CREATED) |
fixed status |
@Valid / @Validated |
bean validation; failure -> MethodArgumentNotValidException (400) |
@CrossOrigin |
CORS on method/class |
@ControllerAdvice / @RestControllerAdvice |
global @ExceptionHandler, @InitBinder |
- Boot 3 / Spring 6:
jakarta.* packages (not javax.*); trailing-slash match is off by default.
- Boot 3.2+:
RestClient (sync fluent client) alongside WebClient; RestTemplate is in maintenance mode.
Transaction annotations
| Attribute |
Default |
Notes |
propagation |
REQUIRED |
join existing or start new |
|
REQUIRES_NEW |
suspend outer, independent commit/rollback |
|
NESTED |
savepoint (JDBC only; not JPA with Hibernate by default) |
|
SUPPORTS, NOT_SUPPORTED, MANDATORY, NEVER |
|
isolation |
DEFAULT |
DB default |
readOnly |
false |
hint; Hibernate sets flush mode MANUAL, skips dirty checking |
rollbackFor |
unchecked only |
rolls back on RuntimeException and Error; checked exceptions commit unless listed |
timeout |
none |
seconds |
- Proxy-based: self-invocation (
this.other()), private/final methods and non-Spring-managed objects bypass @Transactional.
- Put
@Transactional on service methods, not on controllers/repositories; @Transactional on interface methods works with JDK proxies only.
- Catching an exception inside a
@Transactional method and swallowing it keeps the transaction committing; inner REQUIRED rollback marks the whole tx rollback-only (UnexpectedRollbackException).
@TransactionalEventListener(phase = AFTER_COMMIT) for post-commit side effects.
Bean scopes
| Scope |
Lifetime |
singleton (default) |
one per container (not per JVM) |
prototype |
new instance per lookup; destroy callbacks not called by Spring |
request, session, application, websocket |
web scopes; inject into singletons via scoped proxy (proxyMode = TARGET_CLASS) |
- A prototype injected into a singleton is created once; use
ObjectProvider<T> or @Lookup.
- Singleton beans must be thread-safe (stateless or synchronized state).
Bean lifecycle order
1 instantiate (constructor)
2 populate properties / inject dependencies
3 Aware callbacks (BeanNameAware, BeanFactoryAware, ApplicationContextAware)
4 BeanPostProcessor.postProcessBeforeInitialization
5 @PostConstruct
6 InitializingBean.afterPropertiesSet
7 custom init-method (@Bean(initMethod = "..."))
8 BeanPostProcessor.postProcessAfterInitialization (AOP proxies created here)
9 bean in use
10 @PreDestroy -> DisposableBean.destroy -> custom destroyMethod
BeanFactoryPostProcessor runs before any bean is instantiated (e.g. property placeholders).
- Startup events:
ApplicationStartingEvent -> ApplicationEnvironmentPreparedEvent -> ContextRefreshedEvent -> ApplicationStartedEvent -> ApplicationReadyEvent. CommandLineRunner/ApplicationRunner run between Started and Ready.
Auto-configuration
- Boot 2.7+/3: candidates listed in
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (replaced spring.factories entries).
- Each is
@AutoConfiguration with @ConditionalOnClass / @ConditionalOnMissingBean / @ConditionalOnProperty, so your own bean wins.
- Inspect:
--debug or debug=true prints the conditions report; Actuator /actuator/conditions.
- Exclude:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) or spring.autoconfigure.exclude.
- Custom starter = autoconfigure module +
.imports file + optional @ConfigurationProperties.
Profiles and property sources
- Activate:
spring.profiles.active=dev,local, --spring.profiles.active=prod, env SPRING_PROFILES_ACTIVE.
- Files:
application.yml, application-{profile}.yml; profile file overrides base. Multi-document YAML uses spring.config.activate.on-profile.
- Precedence (high to low): command-line args,
SPRING_APPLICATION_JSON, OS env vars, application-{profile} (outside jar before inside), application.yml, @PropertySource, defaults.
- Env var mapping:
SPRING_DATASOURCE_URL -> spring.datasource.url; MY_APP_MAX_SIZE -> my.app.max-size.
- Extra location:
spring.config.import=optional:file:./local.yml.
Common properties
server:
port: 8080
shutdown: graceful # with spring.lifecycle.timeout-per-shutdown-phase: 30s
spring:
datasource:
url: jdbc:postgresql://localhost:5432/app
username: app
password: ${DB_PASSWORD}
hikari.maximum-pool-size: 10
jpa:
hibernate.ddl-auto: validate # none|validate|update|create|create-drop
open-in-view: false # default true, warns at startup
properties.hibernate.jdbc.batch_size: 50
jackson.default-property-inclusion: non_null
threads.virtual.enabled: true # Boot 3.2+, Java 21
management.endpoints.web.exposure.include: health,info,prometheus
logging.level.org.hibernate.SQL: debug
REST controller recipe
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor // or explicit constructor
class UserController {
private final UserService service;
@GetMapping("/{id}")
UserDto get(@PathVariable Long id) { return service.get(id); }
@PostMapping
ResponseEntity<UserDto> create(@Valid @RequestBody CreateUser req) {
UserDto dto = service.create(req);
return ResponseEntity.created(URI.create("/api/users/" + dto.id())).body(dto);
}
@DeleteMapping("/{id}") @ResponseStatus(HttpStatus.NO_CONTENT)
void delete(@PathVariable Long id) { service.delete(id); }
}
record CreateUser(@NotBlank String name, @Email String email) {}
- Return DTOs, not entities (lazy proxies, over-exposure, infinite recursion).
- Typical statuses: 201 +
Location on create, 204 on delete, 400 validation, 404 missing, 409 conflict, 422 semantic error.
Exception handling
@RestControllerAdvice
class ApiErrors extends ResponseEntityExceptionHandler {
@ExceptionHandler(NotFoundException.class)
ProblemDetail notFound(NotFoundException e) {
ProblemDetail p = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage());
p.setTitle("Not found");
return p;
}
}
ProblemDetail (RFC 7807/9457) is built into Spring 6; enable for built-in exceptions with spring.mvc.problemdetails.enabled=true.
- Resolution order:
@ExceptionHandler in controller, then @ControllerAdvice, then ResponseStatusExceptionResolver, then DefaultHandlerExceptionResolver, finally /error (BasicErrorController).
- Filters run outside the DispatcherServlet; their exceptions never reach
@ControllerAdvice.
- Validation:
@Valid on @RequestBody -> MethodArgumentNotValidException; on params with @Validated class -> ConstraintViolationException (Spring 6.1: HandlerMethodValidationException).
Testing slices
@SpringBootTest (full context; webEnvironment = RANDOM_PORT), @WebMvcTest(Controller.class) (MVC layer + @MockBean/@MockitoBean in 3.4+), @DataJpaTest (JPA + embedded DB, rolls back), @JsonTest.
- Testcontainers with
@ServiceConnection (Boot 3.1+) replaces manual @DynamicPropertySource.