The annotations you'll use every single day
Stereotype annotations and @Autowired are the most-typed Spring code there is. They look
interchangeable, and interviewers love to probe whether you understand the real differences — which
ones add behavior, how scanning finds them, and why the way you inject matters. This article sorts
it all out.
@Component is the foundation
@Component marks a class as a candidate for component scanning — "create and manage a bean
from this." Every other stereotype is built on it.
@Component
class EmailValidator {
boolean isValid(String email) { return email.contains("@"); }
}
At startup, @ComponentScan (bundled into @SpringBootApplication) walks the package tree, finds
each @Component, and registers a bean definition.
The four stereotypes
@Component, @Service, @Repository, and @Controller all register beans and are functionally
near-identical — but they communicate the class's layer, and two add real behavior:
@Component class GenericHelper { } // generic
@Service class OrderService { } // business logic (documentation)
@Repository class OrderRepository { } // persistence (+ exception translation)
@Controller class OrderController { } // web layer (returns view names)
@Service and @Controller are largely semantic — they read better and let aspects/tools
target a layer. But @Repository is special: it enables persistence exception translation,
converting vendor-specific exceptions into Spring's DataAccessException hierarchy. @Controller
plugs into Spring MVC's request mapping.
@RestController = @Controller + @ResponseBody
For JSON APIs you want @RestController, which makes every method return data in the response
body instead of a view name:
@Controller
class PageController {
@GetMapping("/home")
String home() { return "home"; } // "home" = a VIEW NAME
}
@RestController // = @Controller + @ResponseBody
class ApiController {
@GetMapping("/users")
List<User> users() { return service.all(); } // serialized to JSON
}
Use @Controller for server-rendered HTML, @RestController for REST APIs.
How component scanning finds your beans
@ComponentScan defines where scanning starts — by default, the package of your
@SpringBootApplication class and all sub-packages:
@SpringBootApplication // scans com.example and everything below
class App { }
@ComponentScan(basePackages = { "com.example.web", "com.shared.util" })
class Config { } // override the base packages explicitly
This is exactly why your main class belongs in a root package above the rest of your code —
beans in packages outside that tree won't be found. You can narrow or widen scanning with
includeFilters/excludeFilters, which is how Spring Boot's test slices (@WebMvcTest,
@DataJpaTest) load only the relevant layer.
@Autowired and the three injection styles
@Autowired injects a matching bean by type at a constructor, setter, or field. Spring supports
three styles:
@Service
class A { // 1. constructor (preferred)
private final Repo repo;
A(Repo repo) { this.repo = repo; }
}
@Service
class B { // 2. setter
private Repo repo;
@Autowired void setRepo(Repo repo) { this.repo = repo; }
}
@Service
class C { // 3. field (discouraged)
@Autowired private Repo repo;
}
Since Spring 4.3, @Autowired is optional on a single constructor — Spring autowires its
parameters automatically.
Why constructor injection wins
The Spring team recommends constructor injection, and the reasons are exactly what interviewers want to hear:
@Service
class OrderService {
private final PaymentGateway gateway; // can be final → immutable
private final InventoryService inventory;
OrderService(PaymentGateway g, InventoryService i) { gateway = g; inventory = i; }
}
// Test with no Spring at all:
var svc = new OrderService(mockGateway, mockInventory);
Constructor injection (1) allows final fields → immutability and thread safety; (2) guarantees the
bean is never half-initialized; (3) makes dependencies visible in the signature — a bloated
constructor is a visible smell; (4) needs no Spring or reflection to test. Field injection
hides dependencies, forbids final, and lets the dependency list grow unnoticed. In a plain unit
test, a field-injected dependency is just null.
Useful wiring patterns
A few patterns come up constantly:
// Inject ALL implementations of an interface (Strategy / plugin registry):
@Service
class NotificationService {
NotificationService(List<Notifier> notifiers) { } // every Notifier bean
}
// Make a dependency optional:
@Service
class MetricsService {
MetricsService(Optional<MetricsExporter> exporter) { } // empty if no bean
}
// Pick a default among several candidates:
@Component @Primary class StripeGateway implements PaymentGateway { }
// Vary beans by environment:
@Component @Profile("prod") class SmtpMailSender implements MailSender { }
Injecting List<T> or Map<String,T> gathers all implementations — adding a new strategy is just
adding a new @Component. @Primary sets a default; @Profile swaps implementations per
environment.
@Component vs @Bean
Use @Component when you own the class; use @Bean when you can't annotate it (third-party
types) or need construction logic:
@Service class OrderService { } // your class → stereotype
@Configuration
class Config {
@Bean RestClient restClient() { // library class → @Bean method
return RestClient.builder().baseUrl("https://api.example.com").build();
}
}
Real apps blend both: stereotypes for your code, @Bean methods for external types and custom
wiring.
Recap
@Component is the base stereotype; @Service and @Controller mostly document intent, while
@Repository adds exception translation and @RestController bundles @ResponseBody for APIs.
Component scanning starts at your @SpringBootApplication package, so keep that class at the root.
Inject through constructors for immutable, explicit, testable beans; avoid field injection. And reach
for List<T> injection, @Primary, @Profile, and @Bean methods as the situation demands.