Spring Boot’s Core, Web, Data, Messaging and Testing Annotations

At first glance, Spring Boot appears deceptively simple.
You add @SpringBootApplication, write a controller and suddenly your API is live. But after building a few services, you realize something: annotations express architectural boundaries — they don’t replace architecture..
They define how beans are created, how HTTP requests are mapped, how transactions behave, how validation is triggered and even how your tests start up.
⚡ TL;DR (Quick Recap)
- Spring Boot annotations map directly to architectural layers.
- Constructor injection usually needs no @Autowired since Spring 4.3.
- Web, Data, Messaging, Validation and Testing each have their own core annotation set.
- Clean grouping improves design clarity and maintainability.
Core / Application Setup
These annotations bootstrap and structure your application.
@SpringBootApplication The heart of every Boot app.
It combines:
- @Configuration
- @EnableAutoConfiguration
- @ComponentScan
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
This single annotation activates auto-configuration and component scanning.
Stereotypes: @Component, @Service, @Repository They mark classes as Spring-managed beans.
- @Component – generic bean
- @Service – business logic layer
- @Repository – persistence layer (adds exception translation)
@Service
public class UserService {
}
While technically equivalent to @Component, using semantic stereotypes improves readability.
@Configuration, @Bean, @Profile Used for explicit bean definitions and environment-specific wiring. Scope annotations @Scope("prototype") and @RequestScope.
@Configuration
public class AppConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
return DataSourceBuilder.create()
.url("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1")
.driverClassName("org.h2.Driver")
.build();
}
}
Use this when auto-configuration isn’t enough.
Dependency Injection & Properties
Spring is a dependency injection framework at its core.
@Autowired & @Qualifier
Can be applied to:
- Constructor — for single-constructor classes
- Setter
- Field
Since Spring 4.3, if a class has a single constructor, no annotation is required. Adding final to dependencies is a "must-have" best practice for immutability.
@Service
public class OrderService {
private final PaymentService service;
public OrderService(PaymentService service) {
this.service = service;
}
}
Use @Qualifier when multiple beans of the same type exist.
@Value vs @ConfigurationProperties
Inject configuration values:
@Value("${app.timeout}")
private int timeout;For structured configuration:
In Spring Boot 3+, constructor binding is the default for records.
@ConfigurationProperties(prefix = "app")
public record AppProperties(int timeout) {}
// Option 1: Explicit registration
@EnableConfigurationProperties(AppProperties.class)
// Option 2: Classpath scanning (recommended for larger apps)
@SpringBootApplication
@ConfigurationPropertiesScan
public class Application { }
Prefer @ConfigurationProperties for maintainability and type safety. This requires @ConfigurationPropertiesScan or @EnableConfigurationProperties.
Web / REST
Spring Boot’s web layer is annotation-driven.
@RestController
Combines:
- @Controller
- @ResponseBody
Every method returns data directly (JSON by default).
@RestController
@RequestMapping("/users")
public class UserController {
}
@RequestMapping and shortcuts
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
- @PatchMapping
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}Request & Response Binding Annotations
- @PathVariable → /users/{id}
- @RequestParam → /users?page=1
- @RequestBody → Deserialize JSON into object
- @ResponseBody → Serialize return value, @RestController already applies it globally.
@PostMapping
public User create(@RequestBody @Valid CreateUserRequest request) {
return userService.create(request);
}
@ControllerAdvice& @RestControllerAdvice & @ExceptionHandler
Global error handling:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<Void> handle(UserNotFoundException ex) {
return ResponseEntity.notFound().build();
}
}
Keeps controllers clean and consistent. @RestControllerAdvice automatically applies @ResponseBody.
Data / JPA
Spring Data simplifies persistence dramatically.
@Entity, @Id, @GeneratedValue Defines ORM mapping.
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
@Transactional Controls transaction boundaries.
@Service
public class TransferService {
@Transactional
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
// business logic
}
}
Applies at class or method level. Important in service layer — not controllers. Transactions only work through Spring proxies.
If a method is:
- private
- called from the same class
- outside a proxied bean
It won’t be transactional.
Messaging & Event Streaming
Modern applications are rarely synchronous.
@KafkaListener For consuming Apache Kafka messages:
@KafkaListener(
topics = "orders",
groupId = "order-service"
)
public void handle(OrderEvent event) {
}
@JmsListener For JMS-based brokers:
@JmsListener(destination = "queue.orders")
public void receive(String message) {
}
Spring Application Events Internal event system.
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
}
For transactional guarantees:
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) // default
public void handleAfterCommit(OrderCreatedEvent event) {
// fires only if the surrounding transaction committed successfully
}
Add @Async (requires @EnableAsync) for asynchronous handling.
Validation & Null Safety
Validation is declarative in Spring Boot.
Common Validation Annotations
- @NotNull
- @NotBlank
- @NotEmpty
- @Size
public class CreateUserRequest {
@NotBlank
private String name;
}Triggered via:
@PostMapping
public User create(@RequestBody @Valid CreateUserRequest request) {
return userService.create(request);
}
Null Safety Annotations — from JSpecify
- @NonNull
- @Nullable
- @NullMarked
- @NullUnmarked
Testing Annotations
Testing in Spring Boot is slice-based and powerful.
@SpringBootTest Loads full application context.
@SpringBootTest
class ApplicationTests {
}
@WebMvcTest Loads only MVC layer.
@WebMvcTest(UserController.class)
class UserControllerTest {
}
@MockitoBean & @MockitoSpyBean & @TestConfiguration
- @MockitoBean replaces a bean in context
- @TestConfiguration provides test-specific beans
- In Spring Boot 3.4+, @MockBean and @SpyBean are deprecated in favor of @MockitoBean and @MockitoSpyBean. In Boot ≤3.3, use @MockBean.
Final Takeaways
Spring Boot annotations are not just conveniences. Annotations are not decoration. They are executable architecture. Treat them intentionally.
When grouped properly, they reveal a clean layering model:
- Core setup defines the application structure.
- Dependency injection wires components.
- Web annotations expose APIs.
- Data annotations manage persistence and transactions.
- Messaging and events support asynchronous systems.
- Validation protects data integrity.
- Testing annotations ensure reliability.
The more intentionally you use them, the clearer your application becomes.
Originally posted on marconak-matej.medium.com.