Spring Beans: @Component, @Bean and @Configuration
Introduction
This tutorial explains the ways to declare beans in Spring - the objects the container creates and manages. We will start with@Component and its specialized forms @Service, @Repository and @Controller, and see how bean names are chosen. We will then look at component scanning: how Spring finds those classes, how to point it at the right packages, and how to include or exclude classes with filters. Next we will declare beans with @Bean methods in @Configuration classes - the way to make beans out of classes you cannot annotate - and see the difference between a @Configuration class and a plain one. Lastly, we will combine configuration classes with @Import and decide when to use which approach.(This tutorial assumes you know what the container and a bean are. If not, read the Spring IoC container and ApplicationContextClick to read tutorial: Spring IoC Container and ApplicationContext Explained first.)
Two ways to declare a bean
Spring needs to know which objects to manage. You tell it in one of two ways -- Annotate the class with
@Component(or one of its specializations). Spring finds the class by component scanning and creates the bean itself, using the class's constructor. - Write a factory method annotated with
@Beaninside a configuration class. Spring calls the method, and the object it returns becomes the bean.
@Component for its own classes, @Bean for library classes and for beans that need custom construction. (A third way, XML configuration, is still supported but is not covered in this series.)
@Component and the stereotype annotations
@Component is the general-purpose annotation. Spring also has three stereotype annotations, each itself annotated with @Component, that say what role a class plays -
@Service: business logic. It behaves exactly like@Component; the name documents intent.@Repository: data access. Besides documenting intent, it lets Spring translate database-specific exceptions into Spring'sDataAccessExceptionhierarchy when exception translation is set up (Spring Data and Spring Boot do this for you).@Controller(and@RestController): web request handlers in Spring MVC, which looks for these classes when it maps URLs.
@Component, @Service, @Repository and bean names
//StereotypesApp.java
package com.javabrahman.spring.beans.stereotypes;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
@Repository
class EmployeeRepository {
String findName(int id) {
return id == 1 ? "Deborah Sprightly" : "unknown";
}
}
@Service
class EmployeeService {
private final EmployeeRepository repository;
EmployeeService(EmployeeRepository repository) {
this.repository = repository;
}
String describe(int id) {
return "Employee " + id + " is " + repository.findName(id);
}
}
@Component("clock") // explicit bean name instead of the default "systemClockAdapter"
class SystemClockAdapter {
String now() {
return "09:00";
}
}
@Configuration
@ComponentScan
class AppConfig {
}
public class StereotypesApp {
public static void main(String[] args) {
try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
System.out.println(context.getBean(EmployeeService.class).describe(1));
for (String name : context.getBeanDefinitionNames()) {
if (!name.contains(".")) {
System.out.println(name + " -> " + context.getType(name).getSimpleName());
}
}
}
}
}
Employee 1 is Deborah Sprightly appConfig -> AppConfig employeeRepository -> EmployeeRepository employeeService -> EmployeeService clock -> SystemClockAdapter
Explanation of the code
- Component scanning found all three annotated classes, because
@Serviceand@Repositoryare meta-annotated with@Component. - The default bean name is the simple class name with its first letter in lower case:
EmployeeRepositorybecomesemployeeRepository. @Component("clock")sets the name explicitly. The stereotype annotations accept a name the same way, for example@Service("payroll").- The configuration class itself,
AppConfig, is a bean too:@Configurationis also meta-annotated with@Component.
Component scanning
@ComponentScan on a configuration class tells Spring where to look for component classes. Without attributes it scans the package of the configuration class and all its sub-packages. You can name other packages, and you can add filters that include or exclude classes. The example below has three packages - the application's root package, a data sub-package, and a legacy sub-package whose beans we want to leave out -
com.javabrahman.spring.beans.scan.data - a repository in a sub-package
//Customers.java
package com.javabrahman.spring.beans.scan.data;
import org.springframework.stereotype.Repository;
@Repository
public class Customers {
public int count() {
return 42;
}
}
com.javabrahman.spring.beans.scan.legacy - a component we want to exclude
//OldMailer.java
package com.javabrahman.spring.beans.scan.legacy;
import org.springframework.stereotype.Component;
@Component
public class OldMailer {
public OldMailer() {
System.out.println("OldMailer created - this should not happen");
}
}
Scanning the root package, with an exclude filter
//ScanApp.java
package com.javabrahman.spring.beans.scan;
import com.javabrahman.spring.beans.scan.data.Customers;
import java.util.Arrays;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Service;
@Service
class Dashboard {
private final Customers customers;
Dashboard(Customers customers) {
this.customers = customers;
}
String summary() {
return "Customers: " + customers.count();
}
}
@Configuration
@ComponentScan(
basePackages = "com.javabrahman.spring.beans.scan",
excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX,
pattern = "com\\.javabrahman\\.spring\\.beans\\.scan\\.legacy\\..*"))
class AppConfig {
}
public class ScanApp {
public static void main(String[] args) {
try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
System.out.println(context.getBean(Dashboard.class).summary());
System.out.println("Beans: " + Arrays.stream(context.getBeanDefinitionNames())
.filter(name -> !name.contains(".")).toList());
}
}
}
Customers: 42 Beans: [appConfig, dashboard, customers]
Explanation of the code
basePackagesnames the package to scan; its sub-packages are included, which is howCustomersin thedatasub-package was found and injected intoDashboard.excludeFilterswithFilterType.REGEXremoved every class in thelegacypackage, soOldMailernever became a bean - its constructor message did not print.- Other filter types match by annotation (
FilterType.ANNOTATION), by assignable type (FilterType.ASSIGNABLE_TYPE), by AspectJ pattern, or by your ownTypeFilterclass.includeFiltersworks the other way round and adds classes that have no stereotype annotation. basePackageClasses = SomeClass.classis a type-safe alternative tobasePackages: it scans the package of the given class and still compiles if the package is renamed by a refactoring tool.- Tip: put the configuration class (in Spring Boot, the
@SpringBootApplicationclass) in the root package of the application, so that the default scan covers everything below it and nothing else. Never scan a top-level package such ascom, which would scan every library on the classpath too.
@Bean methods in a @Configuration class
@Component only works for classes you can edit. For a class from a library - here java.time.Clock and java.time.format.DateTimeFormatter from the JDK - you write a @Bean method that creates and configures the object. @Bean methods are also the right choice when creating a bean needs logic, such as choosing an implementation based on a setting -
Declaring beans for JDK classes with @Bean methods
//BeanMethodsApp.java
package com.javabrahman.spring.beans.methods;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
class ReceiptPrinter {
private final Clock clock;
private final DateTimeFormatter formatter;
ReceiptPrinter(Clock clock, DateTimeFormatter formatter) {
this.clock = clock;
this.formatter = formatter;
}
String print(String item) {
return item + " - " + formatter.format(clock.instant().atZone(clock.getZone()));
}
}
@Configuration
class AppConfig {
// A fixed clock, so the example prints the same time on every run
@Bean
Clock clock() {
return Clock.fixed(Instant.parse("2026-11-06T10:15:30Z"), ZoneId.of("Europe/London"));
}
@Bean(name = "receiptDateFormat")
DateTimeFormatter dateFormat() {
return DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm");
}
@Bean
ReceiptPrinter receiptPrinter(Clock clock, DateTimeFormatter formatter) {
return new ReceiptPrinter(clock, formatter);
}
}
public class BeanMethodsApp {
public static void main(String[] args) {
try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
System.out.println(context.getBean(ReceiptPrinter.class).print("Java 8 in Action"));
System.out.println("Formatter bean found by its custom name: "
+ context.containsBean("receiptDateFormat"));
}
}
}
Java 8 in Action - 06 Nov 2026 10:15 Formatter bean found by its custom name: true
Explanation of the code
- Each
@Beanmethod returns the object that becomes the bean. Spring calls it once (for a singleton) and keeps the result. @Bean(name = "receiptDateFormat")overrides the default name, which would have been the method name,dateFormat.- The parameters of
receiptPrinter()are resolved from the container by type: Spring passes in theClockandDateTimeFormatterbeans. ReceiptPrinterhas no Spring annotations at all. A class created by a@Beanmethod does not need to know Spring exists, which keeps it reusable and easy to test.@Beanalso acceptsinitMethodanddestroyMethodattributes, covered in the tutorial on the bean lifecycleClick to read tutorial: Spring Bean Lifecycle: @PostConstruct, @PreDestroy and More.
Why @Configuration classes are special
@Bean methods can also be written in a class annotated only with @Component, or in a @Configuration(proxyBeanMethods = false) class - this is called lite mode. The difference shows when one @Bean method calls another. In a full @Configuration class, Spring subclasses the configuration class at runtime (with CGLIB) and intercepts such calls, so the call returns the existing singleton bean. In lite mode, the call is an ordinary Java method call and creates a second object -
Full @Configuration vs lite mode when @Bean methods call each other
//ConfigurationModesApp.java
package com.javabrahman.spring.beans.modes;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
class ConnectionPool {
private static int created = 0;
final int number = ++created;
}
class OrderDao {
final ConnectionPool pool;
OrderDao(ConnectionPool pool) {
this.pool = pool;
}
}
class InvoiceDao {
final ConnectionPool pool;
InvoiceDao(ConnectionPool pool) {
this.pool = pool;
}
}
@Configuration
class FullConfig {
@Bean
ConnectionPool pool() {
return new ConnectionPool();
}
@Bean
OrderDao orderDao() {
return new OrderDao(pool()); // intercepted: returns the pool bean
}
@Bean
InvoiceDao invoiceDao() {
return new InvoiceDao(pool()); // intercepted: returns the same pool bean
}
}
@Configuration(proxyBeanMethods = false)
class LiteConfig {
@Bean
ConnectionPool pool() {
return new ConnectionPool();
}
@Bean
OrderDao orderDao() {
return new OrderDao(pool()); // plain Java call: a new ConnectionPool
}
@Bean
InvoiceDao invoiceDao() {
return new InvoiceDao(pool()); // plain Java call: another new ConnectionPool
}
}
public class ConfigurationModesApp {
public static void main(String[] args) {
show(FullConfig.class);
show(LiteConfig.class);
}
static void show(Class<?> config) {
try (var context = new AnnotationConfigApplicationContext(config)) {
System.out.println(config.getSimpleName() + " runs as " + context.getBean(config).getClass().getSimpleName());
System.out.println(" pool bean #" + context.getBean(ConnectionPool.class).number
+ ", OrderDao uses pool #" + context.getBean(OrderDao.class).pool.number
+ ", InvoiceDao uses pool #" + context.getBean(InvoiceDao.class).pool.number);
}
}
}
FullConfig runs as FullConfig$$SpringCGLIB$$0 pool bean #1, OrderDao uses pool #1, InvoiceDao uses pool #1 LiteConfig runs as LiteConfig pool bean #2, OrderDao uses pool #3, InvoiceDao uses pool #4
Explanation of the code
FullConfigruns as a generated subclass whose name contains$$SpringCGLIB$$. Itspool()calls were intercepted, so both DAOs share pool #1, the one pool bean.LiteConfigruns as itself, with no subclass. Eachpool()call made a new object, so the context ended up with three pools: the bean (#2) and two private copies (#3 and #4) that the container does not manage. That is a real bug if the pool holds database connections.- The safe pattern in both modes is to take dependencies as
@Beanmethod parameters instead of calling other@Beanmethods, asBeanMethodsAppdid. Lite mode is then perfectly safe, and it starts slightly faster because no subclass is generated - which is why Spring Boot's own auto-configuration classes useproxyBeanMethods = false. - Because of the generated subclass, a full
@Configurationclass must not befinal, and its@Beanmethods must not beprivateorfinal.
Combining configuration classes with @Import
Larger applications split their configuration into several classes - one for persistence, one for messaging, and so on.@Import pulls other configuration classes into one, so the context can still start from a single class -
Splitting configuration into classes and combining them with @Import
//ImportApp.java
package com.javabrahman.spring.beans.imports;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
record DataSourceSettings(String url) {
}
record MailSettings(String host) {
}
@Configuration
class PersistenceConfig {
@Bean
DataSourceSettings dataSourceSettings() {
return new DataSourceSettings("jdbc:postgresql://localhost/shop");
}
}
@Configuration
class MessagingConfig {
@Bean
MailSettings mailSettings() {
return new MailSettings("smtp.example.com");
}
}
@Configuration
@Import({PersistenceConfig.class, MessagingConfig.class})
class AppConfig {
}
public class ImportApp {
public static void main(String[] args) {
try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
System.out.println(context.getBean(DataSourceSettings.class));
System.out.println(context.getBean(MailSettings.class));
}
}
}
DataSourceSettings[url=jdbc:postgresql://localhost/shop] MailSettings[host=smtp.example.com]
Explanation of the code
- The context starts from
AppConfigonly, but@Importregisters the beans ofPersistenceConfigandMessagingConfigtoo. - The beans are recordsClick to read: records - any Java object can be a bean.
- Component scanning would also have found the two configuration classes, since
@Configurationis a@Component.@Importis for configuration that is not in a scanned package, such as configuration shipped in a library, and for making dependencies between configuration classes explicit. Spring Boot's auto-configuration is, at heart, a large and conditional version of this idea.
When to use which
- Your own classes: annotate them with
@Service,@Repository,@Controlleror@Component, and let component scanning find them. This keeps the bean declaration next to the code. - Library classes - clocks, HTTP clients, object mappers, data sources: declare them with
@Beanmethods, because you cannot annotate code you do not own. - Beans that need construction logic - choosing an implementation, reading settings, calling a builder: use
@Beanmethods, where that logic is plain Java. - Configuration classes: group related
@Beanmethods by area, take dependencies as method parameters, and combine the classes with scanning or@Import.
Summary
In this tutorial we first saw the two ways to declare a bean: annotating the class, or writing a@Bean factory method. We used @Component and the stereotypes @Service and @Repository, and saw how default and custom bean names are chosen. We then used component scanning across packages, with a filter that excluded one package. Next we declared beans for JDK classes with @Bean methods, and saw why full @Configuration classes intercept calls between @Bean methods while lite mode does not. Lastly, we combined configuration classes with @Import and summed up when to use each approach. The next tutorial looks at how the container passes beans to each other: constructor, setter and field injectionClick to read tutorial: Spring Dependency Injection: Constructor vs Setter vs Field.Spring Tutorials
Spring IoC Container and ApplicationContext ExplainedClick to read tutorial: Spring IoC Container and ApplicationContext Explained Spring Dependency Injection: Constructor vs Setter vs FieldClick to read tutorial: Spring Dependency Injection: Constructor vs Setter vs Field What is Spring? Spring Framework vs Spring Boot ExplainedClick to read tutorial: What is Spring? Spring Framework vs Spring Boot Explained Spring Bean Scopes: Singleton, Prototype, Request, SessionClick to read tutorial: Spring Bean Scopes: Singleton, Prototype, Request, Session Spring Bean Lifecycle: @PostConstruct, @PreDestroy and MoreClick to read tutorial: Spring Bean Lifecycle: @PostConstruct, @PreDestroy and More Spring @Qualifier, @Primary and Conditional BeansClick to read tutorial: Spring @Qualifier, @Primary and Conditional Beans Spring Expression Language (SpEL) and @Value ExplainedClick to read tutorial: Spring Expression Language (SpEL) and @Value Explained
Spring IoC Container and ApplicationContext ExplainedClick to read tutorial: Spring IoC Container and ApplicationContext Explained Spring Dependency Injection: Constructor vs Setter vs FieldClick to read tutorial: Spring Dependency Injection: Constructor vs Setter vs Field What is Spring? Spring Framework vs Spring Boot ExplainedClick to read tutorial: What is Spring? Spring Framework vs Spring Boot Explained Spring Bean Scopes: Singleton, Prototype, Request, SessionClick to read tutorial: Spring Bean Scopes: Singleton, Prototype, Request, Session Spring Bean Lifecycle: @PostConstruct, @PreDestroy and MoreClick to read tutorial: Spring Bean Lifecycle: @PostConstruct, @PreDestroy and More Spring @Qualifier, @Primary and Conditional BeansClick to read tutorial: Spring @Qualifier, @Primary and Conditional Beans Spring Expression Language (SpEL) and @Value ExplainedClick to read tutorial: Spring Expression Language (SpEL) and @Value Explained