In Spring, a bean is an object managed by the Spring container.
Spring is responsible for:
- creating the object
- injecting its dependencies
- managing its lifecycle
- applying configuration
- destroying it when the application shuts down
The container that manages beans is usually the ApplicationContext.
1. What Is a Spring Bean?
A Spring bean is just a normal Java object whose lifecycle is controlled by Spring.
For example:
@Service
public class UserService {
public String getMessage() {
return "Hello from UserService";
}
}
UserService is an ordinary Java class, but because it is annotated with @Service, Spring detects it and manages it as a bean.
2. Common Ways to Create Beans
There are two main ways to create beans in Spring:
- Component scanning
- Manual bean registration using
@Bean
Option 1: Create Beans with Component Scanning
This is the most common approach.
Spring scans your project for classes annotated with stereotypes such as:
@Component
@Service
@Repository
@Controller
@RestController
Example:
@Service
public class EmailService {
public void sendEmail(String to, String message) {
System.out.println("Sending email to " + to + ": " + message);
}
}
Spring automatically creates an EmailService bean.
Common Bean Annotations
@Component
Generic Spring-managed component.
@Component
public class FileStorage {
}
Use this when the class does not fit a more specific role.
@Service
Used for service/business logic classes.
@Service
public class PaymentService {
}
@Repository
Used for data access classes.
@Repository
public class UserRepository {
}
In Spring Data JPA, repositories are often interfaces:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
Spring Data JPA creates the implementation automatically.
@Controller
Used for Spring MVC controllers that return views.
@Controller
public class PageController {
}
@RestController
Used for REST APIs.
@RestController
@RequestMapping("/api/users")
public class UserController {
}
@RestController is effectively @Controller plus @ResponseBody.
Option 2: Create Beans Manually with @Bean
Use @Bean when you want to create an object yourself and give it to Spring.
This is common for:
- third-party classes
- library objects
- objects requiring special construction logic
- configuration-based objects
Example:
@Configuration
public class AppConfig {
@Bean
public Clock clock() {
return Clock.systemUTC();
}
}
Now Spring manages a Clock bean.
You can inject it elsewhere:
@Service
public class TimeService {
private final Clock clock;
public TimeService(Clock clock) {
this.clock = clock;
}
public Instant now() {
return Instant.now(clock);
}
}
@Component vs @Bean
Use @Component, @Service, or @Repository when the class is yours and should always be managed by Spring.
Use @Bean when you need explicit construction logic.
Example:
@Configuration
public class HttpClientConfig {
@Bean
public HttpClient httpClient() {
return HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
}
}
Here, HttpClient comes from the JDK, so you cannot annotate it with @Component.
3. Injecting Beans
Once Spring manages a bean, you usually use it through dependency injection.
The recommended style is constructor injection.
@Service
public class OrderService {
private final PaymentService paymentService;
private final EmailService emailService;
public OrderService(PaymentService paymentService, EmailService emailService) {
this.paymentService = paymentService;
this.emailService = emailService;
}
public void placeOrder() {
paymentService.charge();
emailService.sendConfirmation();
}
}
Spring sees that OrderService needs PaymentService and EmailService, then injects them automatically.
Constructor Injection with Lombok
If your project uses Lombok, you can write:
@Service
@RequiredArgsConstructor
public class OrderService {
private final PaymentService paymentService;
private final EmailService emailService;
public void placeOrder() {
paymentService.charge();
emailService.sendConfirmation();
}
}
@RequiredArgsConstructor generates the constructor for all final fields.
This is common in modern Spring applications.
4. Avoid Field Injection
You may see this style:
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
}
This works, but it is usually discouraged because:
- it makes testing harder
- dependencies are hidden
- fields cannot be
final - objects can be created in an invalid state
Prefer constructor injection instead.
5. Bean Names
Every bean has a name.
By default, Spring uses the class name with a lowercase-first letter.
@Service
public class PaymentService {
}
Default bean name:
paymentService
You can also give a custom name:
@Service("stripePaymentService")
public class StripePaymentService {
}
Or with @Bean:
@Bean("utcClock")
public Clock clock() {
return Clock.systemUTC();
}
6. Handling Multiple Beans of the Same Type
If Spring finds multiple beans of the same type, the injection becomes ambiguous.
Example:
public interface PaymentProcessor {
void process();
}
@Service
public class StripePaymentProcessor implements PaymentProcessor {
@Override
public void process() {
System.out.println("Processing with Stripe");
}
}
@Service
public class PaypalPaymentProcessor implements PaymentProcessor {
@Override
public void process() {
System.out.println("Processing with PayPal");
}
}
This is ambiguous:
@Service
public class CheckoutService {
public CheckoutService(PaymentProcessor paymentProcessor) {
}
}
Spring does not know which PaymentProcessor to inject.
Use @Primary
Mark one implementation as the default:
@Service
@Primary
public class StripePaymentProcessor implements PaymentProcessor {
@Override
public void process() {
System.out.println("Processing with Stripe");
}
}
Now Spring injects StripePaymentProcessor unless told otherwise.
Use @Qualifier
Choose a specific bean:
@Service
public class CheckoutService {
private final PaymentProcessor paymentProcessor;
public CheckoutService(
@Qualifier("paypalPaymentProcessor") PaymentProcessor paymentProcessor
) {
this.paymentProcessor = paymentProcessor;
}
}
The qualifier usually matches the bean name.
7. Bean Scopes
By default, Spring beans are singleton scoped.
That means Spring creates one shared instance per application context.
@Service
public class UserService {
}
This is equivalent to:
@Scope("singleton")
@Service
public class UserService {
}
Common Bean Scopes
singleton
One instance per Spring container.
@Component
@Scope("singleton")
public class AppCache {
}
This is the default.
prototype
A new instance each time the bean is requested.
@Component
@Scope("prototype")
public class ReportBuilder {
}
request
One instance per HTTP request.
@Component
@RequestScope
public class RequestContext {
}
Useful in Spring MVC applications.
session
One instance per HTTP session.
@Component
@SessionScope
public class ShoppingCart {
}
8. Bean Lifecycle
Spring beans go through a lifecycle:
1. Bean definition discovered
2. Object created
3. Dependencies injected
4. Initialization callbacks run
5. Bean is ready to use
6. Destruction callbacks run when context closes
Initialization with @PostConstruct
With Jakarta imports, use:
import jakarta.annotation.PostConstruct;
@Service
public class CacheService {
@PostConstruct
public void init() {
System.out.println("CacheService initialized");
}
}
Cleanup with @PreDestroy
import jakarta.annotation.PreDestroy;
@Service
public class CacheService {
@PreDestroy
public void shutdown() {
System.out.println("CacheService shutting down");
}
}
9. Conditional Beans
Sometimes you only want a bean to exist under certain conditions.
In Spring Boot, common annotations include:
@ConditionalOnProperty
@ConditionalOnMissingBean
@ConditionalOnClass
@Profile
Example with profiles:
@Service
@Profile("dev")
public class DevEmailService implements EmailService {
}
@Service
@Profile("prod")
public class SmtpEmailService implements EmailService {
}
Run with:
spring.profiles.active=prod
Then only the prod bean is active.
10. Configuration Properties as Beans
For application configuration, prefer configuration properties instead of manually reading values.
@ConfigurationProperties(prefix = "mail")
public class MailProperties {
private String host;
private int port;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
Enable it:
@Configuration
@EnableConfigurationProperties(MailProperties.class)
public class MailConfig {
}
Example config:
mail.host=smtp.example.com
mail.port=587
Then inject it:
@Service
public class MailService {
private final MailProperties mailProperties;
public MailService(MailProperties mailProperties) {
this.mailProperties = mailProperties;
}
}
11. Getting Beans Programmatically
Most of the time, you should not call ApplicationContext#getBean() manually.
Prefer this:
@Service
public class ReportService {
private final CsvExporter csvExporter;
public ReportService(CsvExporter csvExporter) {
this.csvExporter = csvExporter;
}
}
Instead of this:
@Service
public class ReportService {
private final ApplicationContext applicationContext;
public ReportService(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void export() {
CsvExporter exporter = applicationContext.getBean(CsvExporter.class);
}
}
Programmatic lookup is sometimes useful for dynamic behavior, but it should not be your default approach.
12. Dynamic or Lazy Bean Access
If you need lazy or optional access, prefer ObjectProvider.
@Service
public class NotificationService {
private final ObjectProvider<SmsSender> smsSenderProvider;
public NotificationService(ObjectProvider<SmsSender> smsSenderProvider) {
this.smsSenderProvider = smsSenderProvider;
}
public void notifyUser(String phoneNumber, String message) {
SmsSender smsSender = smsSenderProvider.getIfAvailable();
if (smsSender != null) {
smsSender.send(phoneNumber, message);
}
}
}
This avoids directly depending on ApplicationContext.
13. Lazy Beans
By default, singleton beans are usually created during application startup.
You can make a bean lazy:
@Service
@Lazy
public class ExpensiveService {
}
Or inject it lazily:
@Service
public class DashboardService {
private final ExpensiveService expensiveService;
public DashboardService(@Lazy ExpensiveService expensiveService) {
this.expensiveService = expensiveService;
}
}
14. Managing Beans in Tests
In Spring tests, beans can be injected into test classes:
@SpringBootTest
class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
void placesOrder() {
orderService.placeOrder();
}
}
You can replace beans with mocks using Spring Boot testing support:
@SpringBootTest
class OrderServiceTest {
@MockBean
private PaymentService paymentService;
@Autowired
private OrderService orderService;
@Test
void placesOrder() {
orderService.placeOrder();
}
}
For plain unit tests, you often do not need Spring:
class OrderServiceTest {
@Test
void placesOrder() {
PaymentService paymentService = mock(PaymentService.class);
EmailService emailService = mock(EmailService.class);
OrderService orderService = new OrderService(paymentService, emailService);
orderService.placeOrder();
}
}
15. Practical Rules
Use these rules most of the time:
- Use
@Servicefor business logic. - Use
@Repositoryfor persistence/data access. - Use
@Controlleror@RestControllerfor web endpoints. - Use
@Componentfor general Spring-managed classes. - Use
@Beanfor third-party objects or special construction logic. - Prefer constructor injection.
- Avoid field injection.
- Avoid calling
ApplicationContext#getBean()unless you truly need dynamic lookup. - Use
@Qualifieror@Primarywhen multiple beans share the same type. - Keep singleton beans stateless when possible.
Minimal Example
@Service
public class GreetingService {
public String greet(String name) {
return "Hello, " + name;
}
}
@RestController
@RequestMapping("/greetings")
public class GreetingController {
private final GreetingService greetingService;
public GreetingController(GreetingService greetingService) {
this.greetingService = greetingService;
}
@GetMapping("/{name}")
public String greet(@PathVariable String name) {
return greetingService.greet(name);
}
}
Spring will:
1. Find GreetingService
2. Create a GreetingService bean
3. Find GreetingController
4. Create a GreetingController bean
5. Inject GreetingService into GreetingController
6. Map GET /greetings/{name}
7. Call the controller method when a request arrives
Bottom Line
To create and manage beans in Spring:
- annotate your classes with
@Component,@Service,@Repository, or@Controller - define special beans with
@Beaninside@Configuration - inject dependencies through constructors
- let Spring manage lifecycle, scopes, configuration, and wiring
In most cases, you should declare what your application needs and let Spring create and connect the objects for you.
