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

Spring Boot Annotations You Use Every Day

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
  • @Email
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.