What’s the Real Difference Between @Configuration‑Based @Bean and @Component in Spring?

The article demonstrates that @Configuration classes are CGLIB‑proxied, ensuring @Bean methods are intercepted so internal calls respect container singleton semantics, whereas @Component classes lack this proxy, causing each @Bean call to create a new instance, and explains when to use each approach.

CodeNotes
CodeNotes
CodeNotes
What’s the Real Difference Between @Configuration‑Based @Bean and @Component in Spring?

First, two equivalent‑looking code snippets are shown: one class annotated with @Configuration and another with @Component, each defining @Bean methods for UserService and OrderService. Although the source looks identical, the runtime behavior differs dramatically.

0. How Spring manages beans

When the container starts it scans classes marked with @Configuration, @Component, @Service, @Repository, registers @Bean definitions, instantiates beans, injects dependencies and finally returns fully initialized instances.

1. Core of @Configuration: proxy + CGLIB enhancement

@Configuration

classes are wrapped with a CGLIB proxy; every @Bean method is intercepted. The proxy caches the result of the first invocation, so subsequent calls return the same instance.

@Configuration
public class AppConfig {
    @Bean
    public UserService userService() {
        System.out.println("userService() called");
        return new UserService(orderService());
    }

    @Bean
    public OrderService orderService() {
        System.out.println("orderService() called");
        return new OrderService();
    }
}

Running the application prints:

orderService() called
userService() called

Only one call to orderService() occurs; the UserService receives the same OrderService instance that the container holds.

2. The truth about @Component: plain POJO, no proxy

When the class is annotated with @Component, the @Bean methods are ordinary Java methods. The container does not create a proxy, so an internal call creates a new object each time.

@Component
public class AppConfig {
    @Bean
    public UserService userService() {
        System.out.println("userService() called");
        return new UserService(orderService());
    }

    @Bean
    public OrderService orderService() {
        System.out.println("orderService() called");
        return new OrderService();
    }
}

Output:

orderService() called
userService() called
orderService() called   // second call!

Thus UserService may hold a different OrderService instance from the one stored in the container.

3. Code verification – are the instances the same?

@Configuration
public class ConfigVsComponent {

    @Configuration
    static class ConfigConfig {
        @Bean
        public OrderService orderService() { return new OrderService(); }
        @Bean
        public UserService userService() { return new UserService(orderService()); }
    }

    @Component
    static class ComponentConfig {
        @Bean
        public OrderService orderService() { return new OrderService(); }
        @Bean
        public UserService userService() { return new UserService(orderService()); }
    }

    public static void main(String[] args) {
        // Test @Configuration
        AnnotationConfigApplicationContext ctx1 = new AnnotationConfigApplicationContext(ConfigConfig.class);
        UserService user1 = ctx1.getBean(UserService.class);
        OrderService order1 = ctx1.getBean(OrderService.class);
        System.out.println(user1.orderService == order1);               // true
        // Test @Component
        AnnotationConfigApplicationContext ctx2 = new AnnotationConfigApplicationContext(ComponentConfig.class);
        UserService user2 = ctx2.getBean(UserService.class);
        OrderService order2 = ctx2.getBean(OrderService.class);
        System.out.println(user2.orderService == order2);               // false!
    }
}

4. Why @Configuration needs a proxy

The proxy guarantees that calls between @Bean methods obey the container’s singleton semantics. Without it, each call would create a fresh object, leading to multiple data sources, mismatched transactions, etc.

@Configuration
public class DataSourceConfig {
    @Bean
    public DataSource dataSource() { return new HikariDataSource(); }

    @Bean
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(dataSource()); // proxy ensures same DataSource
    }

    @Bean
    public TransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource()); // same instance
    }
}

5. When to prefer @Component + @Bean

If you need to control bean creation manually or the @Bean methods do not depend on each other, you can place them in a @Component class. The example shows independent beans such as RestTemplate and ObjectMapper.

@Component
public class ExternalServiceConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplateBuilder()
                .setConnectTimeout(Duration.ofSeconds(5))
                .build();
    }

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }
}

6. Common pitfalls

@Bean

methods do not have to be public; they can be protected or package‑private, but they must not be private or final when @Configuration(proxyBeanMethods = true) is used.

Mixing @Configuration and @Component in the same project is possible but discouraged because it makes the proxy behaviour hard to reason about.

The performance overhead of the proxy is negligible (nanoseconds) and is outweighed by the guarantee of correct singleton behavior.

Practical scenarios

Scenario 1 – multiple inter‑dependent beans

@Configuration
public class SecurityConfig {
    @Bean
    public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }

    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
        UserDetails user = User.withUsername("admin")
                .password(passwordEncoder.encode("123456"))
                .roles("ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user);
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider(UserDetailsService userDetailsService,
                                                            PasswordEncoder passwordEncoder) {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder);
        return provider;
    }
}

Scenario 2 – single independent bean

// Either @Component or @Configuration works
@Component  // or @Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        return new RedisTemplate<>();
    }
}
Conclusion: @Configuration creates a CGLIB‑enhanced proxy that preserves container semantics for inter‑bean calls, while @Component leaves @Bean methods as plain methods that may instantiate new objects on each call. For consistency and to avoid subtle bugs, the article recommends using @Configuration for all configuration classes.
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

configurationSpringDependency InjectionBeanCGLIB
CodeNotes
Written by

CodeNotes

Discuss code and AI, and document daily life and personal growth.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.