Hey there! Spring Boot annotations are like magic wands that make your development life easier. They cut down on all the boring boilerplate code and let you focus on the cool stuff. Imagine configuring beans, handling requests, enabling transactions, and managing your app’s behaviour without having to write a single line of code. In this post, we’ll dive into the most popular Spring Boot annotations and show you how they work with some real-world examples.
Core Stereotypes Spring Boot Annotations
@Component
Marks a class as a Spring-managed bean (picked up by component scanning).
@Component
public class EmailValidator {
public boolean isValid(String email) { return email != null && email.contains("@"); }
}
Real world: Utility helpers like validators, formatters, ID generators.
@Service
Business-logic bean (specialized @Component
).
@Service
public class PaymentService {
public void charge(double amount) { /* call gateway */ }
}
Real world: Order, payment, pricing, inventory services.
@Repository
Data-access bean (specialized @Component
); translates persistence exceptions.
@Repository
public interface CustomerRepo extends JpaRepository<Customer, Long> {}
Real world: CRUD access to relational/NoSQL stores.
@Controller
MVC controller returning views (server-side HTML like Thymeleaf).
@Controller
public class PageController {
@RequestMapping("/home") public String home() { return "home"; } // home.html
}
Real world: Traditional web apps with server-rendered pages.
@RestController
@Controller
+ @ResponseBody
→ returns JSON/XML bodies.
@RestController
@RequestMapping("/api/products")
public class ProductApi {
@GetMapping("/{id}") public Product get(@PathVariable long id){ /* ... */ return new Product(); }
}
Real world: REST APIs for SPAs/mobile/microservices.
Web Mappings
@RequestMapping
Generic mapping (supports method, consumes, produces, params).
@RestController
@RequestMapping("/api/customers")
public class CustomerApi {
@RequestMapping(value="/search", method=RequestMethod.GET, params="q")
public List<Customer> search(@RequestParam String q) { /* ... */ return List.of(); }
}
Shortcuts: @GetMapping
, @PostMapping
, @PutMapping
, @DeleteMapping
, @PatchMapping
@PostMapping(consumes="application/json")
public Customer create(@RequestBody Customer c){ /* save */ return c; }
Real world: Clean CRUD-style endpoints.
Common method-argument annotations
@PathVariable
→ extract path piece@RequestParam
→ query/form parameter@RequestBody
→ JSON/XML request body@ResponseStatus(HttpStatus.CREATED)
→ set status code
Dependency Injection
@Autowired
Inject a bean by type (constructor injection preferred).
@Service
public class OrderService {
private final PaymentService payments;
public OrderService(@Autowired PaymentService payments) { this.payments = payments; }
}
Real world: Wire services/repositories without manual new
.
@Qualifier
Disambiguate when multiple beans of the same type exist.
@Service("sms") class SmsNotifier implements Notifier {}
@Service("email") class EmailNotifier implements Notifier {}
@Service
class AlertService {
private final Notifier notifier;
public AlertService(@Qualifier("email") Notifier notifier) { this.notifier = notifier; }
}
Real world: Choose a concrete strategy at injection time.
@Primary
Default bean when multiple candidates exist.
@Configuration
class NotifierConfig {
@Bean @Primary Notifier email() { return new EmailNotifier(); }
@Bean Notifier sms() { return new SmsNotifier(); }
}
Real world: Provide a sensible default but keep alternates.
@Lazy
Create/inject bean lazily (on first use).
@Service
class HeavyService { /* expensive init */ }
@Service
class UsesHeavy {
public UsesHeavy(@Lazy HeavyService heavy) { /* store reference */ }
}
Real world: Speed up startup, break circular dependencies.
@Scope
Control bean scope (singleton
, prototype
, request
, session
).
@Component
@Scope("prototype")
class TempIdGenerator { /* new instance each injection */ }
Real world: Request-scoped web components, prototype tools.
Configuration & Beans
@Configuration
Holds bean factory methods.
@Configuration
public class AppConfig {
@Bean PasswordEncoder encoder(){ return new BCryptPasswordEncoder(); }
}
@Bean
(important note you asked for)
Declare a bean manually, most often for classes from other libraries that are not already Spring beans.
@Configuration
public class HttpConfig {
@Bean public RestTemplate restTemplate(){ return new RestTemplate(); } // 3rd-party type
}
Real world: Register RestTemplate
, ObjectMapper
, HTTP clients, SDKs.
Properties & External Config
@Value
Inject a single property.
@Component
class AppInfo {
@Value("${app.name}") String name;
}
@ConfigurationProperties
(+ @EnableConfigurationProperties
)
Bind a group of properties into a POJO.
// application.properties
// mail.host=smtp.example.com
// mail.port=587
@Component
@ConfigurationProperties(prefix="mail")
class MailProps { private String host; private int port; /* getters/setters */ }
or register explicitly:
@SpringBootApplication
@EnableConfigurationProperties(MailProps.class)
public class App {}
Real world: Clean, type-safe config for modules (DB, mail, cache).
Conditional & Profile-Based Beans
@ConditionalOnProperty
Create a bean only when a property has a given value.
@Configuration
class CacheConfig {
@Bean
@ConditionalOnProperty(name="cache.enabled", havingValue="true", matchIfMissing=false)
CacheManager cacheManager(){ return new ConcurrentMapCacheManager("items"); }
}
Real world: Feature flags like email.enabled
, metrics.enabled
.
@ConditionalOnMissingBean
Provide a bean only if the app hasn’t defined one (great for libraries/starters).
@Configuration
class RestTemplateAutoConfig {
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
RestTemplate restTemplate(){ return new RestTemplate(); }
}
Real world: Library offers a default; app can override by defining its own.
@Profile
Activate beans only for certain environments.
@Profile("dev")
@Configuration
class DevDbConfig { /* H2 datasource */ }
@Profile("prod")
@Configuration
class ProdDbConfig { /* Postgres datasource */ }
Real world: Different infra per dev / test / prod
.
Lifecycle
@PostConstruct
and @PreDestroy
Initialize/cleanup around bean lifecycle.
@Component
class CacheWarmup {
@PostConstruct void init(){ /* load hot data */ }
@PreDestroy void shutdown(){ /* flush metrics/close */ }
}
Real world: Warm caches, start schedulers, release connections.
Scheduling & Async
@EnableScheduling
+ @Scheduled
Enable and declare scheduled jobs.
@SpringBootApplication
@EnableScheduling
class App {}
@Component
class ReportJob {
@Scheduled(cron="0 0 6 * * *") // every day 06:00
void generateDaily(){ /* ... */ }
}
Real world: Cleanup, digests, sync jobs, reports.
@EnableAsync
+ @Async
Run methods in a separate thread.
@SpringBootApplication
@EnableAsync
class App {}
@Service
class Notifier {
@Async public void sendEmail(String to){ /* fire-and-forget */ }
}
Real world: Non-blocking notifications, audits, webhooks.
Transactions & Validation
@Transactional
Demarcate transactions (auto rollback on runtime exceptions).
@Service
public class BankService {
@Transactional
public void transfer(long fromId, long toId, BigDecimal amount){ /* debit/credit */ }
}
Real world: Consistent DB writes across multiple statements.
@Validated
/ @Valid
Trigger Bean Validation (Jakarta Validation) on method params/fields.
@RestController
class UserApi {
@PostMapping("/users")
public User create(@Valid @RequestBody User user){ return user; }
}
Real world: Enforce input rules (email format, size, not null).
Error Handling & CORS
@ExceptionHandler
+ @ControllerAdvice
Centralize API error responses.
@ControllerAdvice
class ApiErrors {
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
Map<String,String> badReq(IllegalArgumentException ex){ return Map.of("error", ex.getMessage()); }
}
Real world: Consistent error payloads across controllers.
@CrossOrigin
Enable CORS for selected endpoints/origins.
@RestController
@CrossOrigin(origins="https://app.example.com")
class PublicApi { /* ... */ }
Real world: Allow browser apps from another domain to call your API.
Data (JPA quick picks)
@Entity
, @Table
, @Id
, @GeneratedValue
Map a class to a DB table.
@Entity @Table(name="customers")
public class Customer {
@Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id;
private String name;
}
Real world: Persist domain models with Spring Data JPA.
Leave a Reply