Why Are Annotations Fundamental to Spring Boot?
Spring Boot replaced heavy XML configurations with declarative Java annotations. Under the hood, annotations act as metadata for the Spring IoC Container and Hibernate bytecode enhancers, instructing the framework on how to construct beans, configure web routes, and map database tables.
Core Bootstrap & Application Context
@SpringBootApplication
Placed on the main class to bootstrap a Spring Boot application. It is a meta-annotation combining three essential annotations:
@Configuration: Tags the class as a source of bean definitions.@EnableAutoConfiguration: Automatically configures beans based on classpath dependencies (Tomcat, Jackson, DataSource).@ComponentScan: Scans the current package and its sub-packages for@Component,@Service,@Repository, and@Controllerclasses.
package com.example.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MySpringBootApplication {
public static void main(String[] args) {
// Starts the Spring ApplicationContext and embedded web server
SpringApplication.run(MySpringBootApplication.class, args);
}
}@Configuration
Marks a class as a configuration source. Methods inside this class use @Bean to instantiate and register beans in the Spring IoC container.
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}@ComponentScan
Specifies custom package locations to scan if your dependencies or shared components live outside the main application package.
@SpringBootApplication
@ComponentScan(basePackages = {"com.example.app", "com.external.shared"})
public class CustomScanApplication {
public static void main(String[] args) {
SpringApplication.run(CustomScanApplication.class, args);
}
}@Bean and @Scope
@Bean: Method-level annotation used to register third-party library objects (such as Jackson ObjectMapper or an S3 client) in the Spring container.
@Scope: Defines bean lifecycle. The default scope is singleton (one instance per Spring Context). Other scopes include prototype (new instance on every injection), request (per HTTP request), and session (per user session).
@Configuration
public class AuditConfig {
@Bean
@Scope("prototype") // New instance created every time someone requests this bean
public AuditLogger auditLogger() {
return new AuditLogger(UUID.randomUUID().toString());
}
@Bean
@Scope("singleton") // Default: Only one instance lives in the entire JVM
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}@EnableAutoConfiguration
Tells Spring Boot to automatically configure sensible defaults based on jars found on the classpath. Unwanted configurations can be disabled using the exclude parameter.
// Disable default DataSource auto-config if we don't need a DB right away
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class ExcludeApplication {
public static void main(String[] args) {
SpringApplication.run(ExcludeApplication.class, args);
}
}Stereotypes & Dependency Injection
@Component, @Service, @Repository
Core stereotype annotations that mark classes for automatic detection during component scanning:
Generic Spring-managed component (utilities, helpers, background workers).
Business logic layer. Clarifies intent for transactional service methods.
Data access layer (DAO). Automatically translates database SQLExceptions into Spring's unified DataAccessException hierarchy.
@Repository
public class OrderRepositoryImpl implements OrderRepository {
// Database access operations
}
@Service
public class OrderService {
// Core business workflows, transaction demarcation
}
@Component
public class CurrencyConverterHelper {
// General utility component
}@Autowired and @Qualifier
@Autowired: Tells Spring to inject dependencies automatically. In modern Spring Boot, constructor injection with final fields is the recommended practice over field injection.
@Qualifier: Resolves ambiguity when multiple beans implement the same interface, preventing NoUniqueBeanDefinitionException.
public interface PaymentGateway {
void pay(double amount);
}
@Component("stripeGateway")
public class StripeGateway implements PaymentGateway { ... }
@Component("paypalGateway")
public class PaypalGateway implements PaymentGateway { ... }
@Service
public class OrderProcessingService {
private final PaymentGateway paymentGateway;
// @Autowired on single constructor is optional in modern Spring, but explicit is good
public OrderProcessingService(@Qualifier("stripeGateway") PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway; // Resolves ambiguity cleanly
}
}Web & REST API Mappings
@RestController vs @Controller & @ResponseBody
The core formula behind modern Spring REST endpoints:@RestController = @Controller + @ResponseBodyWith @Controller, return strings are treated as HTML template names (Thymeleaf/JSP). With @RestController or @ResponseBody, returned objects are serialized directly into the HTTP response body as JSON via Jackson.
@RestController
@RequestMapping("/api/v1/users")
public class UserRestController {
@GetMapping("/{id}")
public UserResponse getUser(@PathVariable Long id) {
return new UserResponse(id, "Shyam", "shyam@example.com");
// Serialized automatically into JSON payload
}
}HTTP Method Mappings & @RequestBody
Standardized mapping shortcuts for routing HTTP requests and extracting payloads:
@RequestMapping: Sets a base URL path at the class level.@GetMapping: Handles HTTP GET requests (reads/fetches data).@PostMapping: Handles HTTP POST requests (creates resources).@PutMapping: Handles HTTP PUT requests (replaces/updates resources).@RequestBody: Deserializes the incoming JSON request body into a typed Java DTO.
@RestController
@RequestMapping("/api/products")
public class ProductEndpointController {
// GET /api/products/42
@GetMapping("/{id}")
public ProductDTO getProduct(@PathVariable Long id) { ... }
// POST /api/products (Takes JSON body and converts to CreateProductRequest DTO)
@PostMapping
public ResponseEntity<ProductDTO> createProduct(@RequestBody CreateProductRequest request) {
ProductDTO saved = productService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).body(saved);
}
// PUT /api/products/42
@PutMapping("/{id}")
public ResponseEntity<ProductDTO> updateProduct(
@PathVariable Long id,
@RequestBody UpdateProductRequest request
) {
return ResponseEntity.ok(productService.update(id, request));
}
}@EnableWebMvc
@EnableWebMvc in Spring Boot applications if you want to keep Spring Boot's automatic Web MVC configuration. This annotation switches off Boot's default auto-configuration for static resources and JSON message converters, giving you a completely blank setup.Use this only when configuring pure Spring MVC without Spring Boot.
Configuration & Environment Properties
@Value vs @ConfigurationProperties
@Value: Injects individual property values from application.properties with SpEL expression support.
@ConfigurationProperties: Binds hierarchical, type-safe configuration groups (nested objects, maps, lists). Recommended for production configs.
@PropertySource: Loads custom configuration files outside the default application.properties file.
// 1. Using @Value for simple single values
@Component
public class NotificationService {
@Value("${app.mail.sender:noreply@company.com}")
private String senderEmail;
}
// 2. Type-Safe Configuration Properties (Best Practice for complex configs)
@Configuration
@ConfigurationProperties(prefix = "payment.gateway")
public class PaymentGatewayConfig {
private String apiKey;
private int timeoutSeconds;
private boolean sandboxMode;
// getters and setters...
}@ConditionalOnProperty
Used for feature flags and conditional bean instantiation. The bean is created only when the specified property key matches the expected value in application.properties.
@Service
@ConditionalOnProperty(name = "feature.mock-sms.enabled", havingValue = "true", matchIfMissing = false)
public class MockSmsService implements SmsService {
@Override
public void send(String phone, String message) {
System.out.println("Mock SMS logged: " + message);
}
}JPA Entities & Lifecycle Callbacks
Entity Core Mapping
@Entity
@Table(name = "customers")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "email_address", nullable = false, unique = true, length = 150)
private String email;
@Column(name = "account_balance", precision = 12, scale = 2)
private BigDecimal balance;
// Field will NOT be saved to the database table!
@Transient
private String temporaryAuthToken;
}@EntityListeners & Lifecycle Callbacks
Automated lifecycle callback hooks triggered during entity operations (insert, update, delete):
@PrePersist: Runs before an insert SQL query executes (e.g., populating createdAt timestamps).@PostPersist: Runs immediately after a database row is inserted.@PreUpdate: Runs before an existing row is updated in the database (e.g., updating updatedAt timestamps).@PostUpdate: Runs after an update commit completes.@PreRemove: Runs before a row is deleted.@PostRemove: Runs after a row is successfully deleted.@EntityListeners: Delegates entity lifecycle callbacks to reusable listener classes (such asAuditingEntityListener.class).
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseAuditEntity {
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@PrePersist
public void onBeforeInsert() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
@PreUpdate
public void onBeforeUpdate() {
this.updatedAt = LocalDateTime.now();
}
@PostRemove
public void onAfterDelete() {
System.out.println("Entity record removed from database.");
}
}JPA Table Relationships: @OneToOne, @OneToMany, @ManyToMany
Mapping Relational Foreign Keys in Java
Primary annotations for mapping relational database foreign keys:
Maps a single relationship between two tables (e.g., a User and their UserProfile). @JoinColumn defines the foreign key column on the owning entity.
Bidirectional parent-child relationship (e.g., an Order and its OrderItems). Best practice is to make the child entity (OrderItem) the owning side with @ManyToOne, and use mappedBy on the parent (Order) side.
Maps relationships between entities where both sides can reference multiple records (e.g., Students and Courses). Uses @JoinTable to define the junction table.
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// One order has many items. 'order' is the field name inside OrderItem
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();
}
@Entity
@Table(name = "order_items")
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// Owning side holding the foreign key column 'order_id'
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id", nullable = false)
private Order order;
}Queries & Transactions: @Query, @Param, @Transactional
@Query and @Param
Spring Data JPA method naming conventions handle simple queries, but @Query is used for complex joins or updates. JPQL (Java Persistence Query Language) queries target Java entity classes and fields, while nativeQuery = true executes raw database SQL.@Param binds method arguments to named parameters in the query (:status).
public interface OrderRepository extends JpaRepository<Order, Long> {
// JPQL Query targeting Java Entity fields
@Query("SELECT o FROM Order o WHERE o.customer.id = :customerId AND o.status = :status")
List<Order> findActiveCustomerOrders(
@Param("customerId") Long customerId,
@Param("status") OrderStatus status
);
// Native Raw SQL Query with database-specific syntax
@Query(value = "SELECT * FROM orders WHERE created_at < NOW() - INTERVAL '30 days'", nativeQuery = true)
List<Order> findStaleOrders();
}@Transactional
Guarantees atomic execution (ACID properties) for database operations. If an unhandled exception occurs during method execution, all database changes are rolled back automatically.
RuntimeException). To roll back on checked exceptions as well, always specify @Transactional(rollbackFor = Exception.class).@Service
public class BankTransferService {
private final AccountRepository accountRepo;
@Transactional(rollbackFor = Exception.class)
public void transferMoney(Long fromAccId, Long toAccId, BigDecimal amount) {
// Step 1: Deduct from source
accountRepo.deductBalance(fromAccId, amount);
// Simulated crash or failure
if (amount.compareTo(new BigDecimal("1000000")) > 0) {
throw new IllegalStateException("Transfer limit exceeded. Aborting!");
}
// Step 2: Credit to destination
accountRepo.creditBalance(toAccId, amount);
// If an exception occurs, Step 1 is rolled back completely!
}
}Summary: Quick Decision Matrix
| Task / Scenario | Recommended Annotation | Key Advantage |
|---|---|---|
| REST API Controller | @RestController | Combines @Controller and @ResponseBody into one clean declaration. |
| Inject 3rd party class as Bean | @Configuration + @Bean | Allows wrapping un-annotated library classes (like ObjectMapper). |
| Multiple Beans of Same Interface | @Qualifier | Eliminates ambiguity when injecting beans. |
| Atomic multi-step DB changes | @Transactional | Guarantees all-or-nothing rollback on exceptions. |
| Temporary non-persisted DB field | @Transient | Prevents Hibernate from creating database columns for memory-only fields. |
Did you find this Spring Boot Annotations handbook helpful?
Share this reference with fellow Java developers building clean, enterprise-ready Spring Boot services!