Back to Bean Types & Stereotypes
Q1Question 1 of 2

What is the difference between Spring bean and plain Java object?

Key Concepts
  • A Spring bean is container-managed: it gets DI, AOP proxying, lifecycle callbacks (@PostConstruct, @PreDestroy), and scope management.
  • An object created with new bypasses the container: @Transactional does nothing, @Autowired fields are null, @PostConstruct never runs.
  • @Component is the generic stereotype; @Service, @Repository, and @Controller are specialized @Component annotations.
  • @Service is purely semantic with no extra behavior beyond marking business logic.
  • @Repository adds persistence-exception translation: vendor-specific exceptions are mapped to Spring's DataAccessException hierarchy.
  • @Controller enables MVC request handling; @RestController adds @ResponseBody for JSON APIs.
  • @Bean goes on a factory method in a @Configuration class for third-party classes you cannot annotate with @Component.

Definition

A Spring bean is container-managed with DI, AOP proxy, and lifecycle hooks; an object created with new has none of these; @Repository adds exception translation; @Bean is for third-party classes you cannot annotate.
  • Spring bean -- an object whose lifecycle is managed by the IoC container, receiving dependency injection, AOP proxy wrapping, @PostConstruct/@PreDestroy callbacks, and scope management.
  • Plain object -- an object created with new, bypassing the container entirely; @Transactional silently does nothing, @Autowired fields remain null.
  • @Component -- the generic stereotype annotation that marks a class for component scanning and bean registration.
  • @Service -- a specialized @Component that marks business logic; purely semantic with no extra behavior.
  • @Repository -- a specialized @Component that marks data access and adds persistence-exception translation via PersistenceExceptionTranslationPostProcessor.
  • @Bean -- placed on a factory method in a @Configuration class to register third-party classes (ObjectMapper, RestTemplate, DataSource) that you cannot annotate.

A Spring bean is an officially hired employee at National Bank of India: HR onboarding, ID badge, assigned desk, orientation training, and a formal exit process. A new object is a freelancer who walks in and out with no records, no badge, and no exit interview. @Service is a business officer. @Repository is the vault keeper who translates vault-specific error codes into standard bank error codes. @Controller is the receptionist who directs customers. @Bean is how the bank hires equipment from external vendors.

Understanding the Concepts

What is the difference between a Spring bean and a plain object?

A Spring bean is created and managed by the IoC container. It receives:

  • Dependency injection -- @Autowired fields and constructor parameters are resolved.
  • AOP proxy wrapping -- @Transactional, @Cacheable, @Async are applied through a CGLIB proxy.
  • Lifecycle callbacks -- @PostConstruct runs after injection, @PreDestroy runs on shutdown.
  • Scope management -- singleton, prototype, request, session scopes are enforced.

An object created with new has none of these. This is the single most common Spring mistake.

JAVA
package com.bank.service;

import com.bank.repository.AccountRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;

@Service
public class TransferService {
    @Autowired
    private AccountRepository repo;

    @Transactional
    public void transfer(String from, String to, BigDecimal amount) {
        var sender = repo.findById(from).orElseThrow();
        sender.debit(amount);
        repo.save(sender);
    }
}
JAVA
package com.bank.controller;

import com.bank.service.TransferService;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;

@RestController
public class TransferController {

    @PostMapping("/transfers")
    public String badTransfer() {
        // WRONG -- creating with new bypasses the container
        TransferService service = new TransferService();
        // service.repo is NULL -- @Autowired never ran
        // @Transactional does nothing -- no AOP proxy
        service.transfer("SAV2001", "SAV2002", new BigDecimal("10000"));
        // NullPointerException at repo.findById()
        return "done";
    }
}
TEXT
java.lang.NullPointerException: Cannot invoke "AccountRepository.findById()"
  because "this.repo" is null

The fix is to let the container inject the service:

JAVA
@RestController
public class TransferController {
    private final TransferService transferService;

    public TransferController(TransferService transferService) {
        this.transferService = transferService; // container provides it
    }

    @PostMapping("/transfers")
    public String goodTransfer() {
        transferService.transfer("SAV2001", "SAV2002", new BigDecimal("10000"));
        return "done"; // works: repo injected, @Transactional active
    }
}

What are the stereotype annotations?

All four stereotype annotations inherit from @Component. They mark classes for component scanning and give them semantic meaning.

| Annotation | Layer | Extra behavior |
|---|---|---|
| @Component | Generic | None -- base annotation |
| @Service | Business logic | None -- purely semantic |
| @Repository | Data access | Exception translation |
| @Controller | Web (returns views) | Enables @RequestMapping |
| @RestController | Web (returns JSON) | @Controller + @ResponseBody |

JAVA
package com.bank;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.RestController;

@Component     // generic: utility classes, converters, validators
public class AccountNumberValidator { }

@Service       // business logic layer
public class LoanApprovalService { }

@Repository    // data access layer + exception translation
public class JpaAccountRepository { }

@RestController // web layer: @Controller + @ResponseBody
public class AccountController { }

How does @Repository enable exception translation?

@Repository activates PersistenceExceptionTranslationPostProcessor, which wraps the repository in a proxy that catches vendor-specific exceptions and translates them into Spring's portable DataAccessException hierarchy.

Without this translation, switching from MySQL to PostgreSQL or from JPA to JDBC could change the exception types your service layer catches. With exception translation, the service layer always catches DataAccessException regardless of the persistence technology.

JAVA
package com.bank.repository;

import com.bank.model.Account;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Repository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.PersistenceException;

@Repository // enables exception translation
public class JpaAccountRepository {
    @PersistenceContext
    private EntityManager em;

    public void save(Account account) {
        // If a duplicate key occurs:
        // JPA throws: PersistenceException (vendor-specific)
        // Spring translates to: DuplicateKeyException (DataAccessException)
        em.persist(account);
    }
}
JAVA
package com.bank.service;

import com.bank.model.Account;
import com.bank.repository.JpaAccountRepository;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;

@Service
public class AccountService {
    private final JpaAccountRepository repo;

    public AccountService(JpaAccountRepository repo) {
        this.repo = repo;
    }

    public void createAccount(String id, BigDecimal initialBalance) {
        try {
            repo.save(new Account(id, initialBalance));
        } catch (DuplicateKeyException e) {
            // Works with JPA, JDBC, or MongoDB -- same exception
            throw new AccountAlreadyExistsException("Account " + id + " exists");
        }
    }
}
DIAGRAM
Exception translation chain:
  Hibernate: ConstraintViolationException
    → JPA: PersistenceException
      → Spring: DuplicateKeyException (DataAccessException)

How is @Bean different from @Component?

@Component goes on the class itself and requires component scanning. @Bean goes on a factory method inside a @Configuration class and is used for classes you cannot annotate, such as third-party libraries.

| Aspect | @Component | @Bean |
|---|---|---|
| Placed on | Class definition | Factory method in @Configuration |
| Scanning | Auto-detected by @ComponentScan | Explicitly declared |
| Use case | Your own classes | Third-party classes (ObjectMapper, DataSource) |
| Customization | Limited | Full control over instantiation |
| Bean name | Class name (camelCase) | Method name |

JAVA
package com.bank.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JacksonConfig {

    @Bean // ObjectMapper is a third-party class -- cannot use @Component
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return mapper;
    }
}
JAVA
package com.bank.config;

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    @Bean(destroyMethod = "close") // HikariCP cleanup
    public DataSource dataSource() {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:postgresql://localhost:5432/nbi_banking");
        ds.setUsername("rajesh");
        ds.setPassword("secret");
        ds.setMaximumPoolSize(20);
        return ds;
    }
}

What is the difference between @Configuration and @Component for @Bean methods?

@Bean methods inside @Configuration classes run in "full mode": Spring ensures that calling one @Bean method from another returns the same singleton instance (CGLIB proxy interception). @Bean methods inside @Component classes run in "lite mode": inter-bean references create new instances each time.

JAVA
package com.bank.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // FULL mode: @Bean methods return the same singleton
public class BankConfig {

    @Bean
    public AccountRepository accountRepository() {
        return new JpaAccountRepository(dataSource()); // same DataSource
    }

    @Bean
    public AuditRepository auditRepository() {
        return new JpaAuditRepository(dataSource()); // SAME DataSource singleton
    }

    @Bean
    public DataSource dataSource() {
        return new HikariDataSource(); // called once, cached by CGLIB proxy
    }
}

If BankConfig were annotated with @Component instead of @Configuration, each call to dataSource() would create a NEW HikariDataSource, resulting in two separate connection pools.

What are common mistakes with stereotypes?

WRONG -- creating a Spring bean with new:

JAVA
// If you see this anywhere outside a test, it is a bug
TransferService service = new TransferService();
service.transfer("SAV2001", "SAV2002", new BigDecimal("10000"));
// @Transactional: does nothing
// @Autowired repo: null
// @PostConstruct: never ran

WRONG -- using @Service on a repository class:

JAVA
@Service // WRONG: no exception translation
public class JpaAccountRepository {
    // Vendor exceptions pass through unhandled
}

CORRECT -- use the right stereotype:

JAVA
@Repository // CORRECT: exception translation enabled
public class JpaAccountRepository {
    // PersistenceException -> DataAccessException
}

WRONG -- @Bean method in @Component class:

JAVA
@Component // lite mode: inter-bean references create NEW instances
public class AppConfig {
    @Bean
    public DataSource dataSource() { return new HikariDataSource(); }

    @Bean
    public AccountRepository repo() {
        return new JpaAccountRepository(dataSource()); // NEW DataSource!
    }

    @Bean
    public AuditRepository audit() {
        return new JpaAuditRepository(dataSource()); // ANOTHER new DataSource!
    }
}

CORRECT -- use @Configuration for full mode:

JAVA
@Configuration // full mode: singleton semantics for @Bean methods
public class AppConfig {
    @Bean
    public DataSource dataSource() { return new HikariDataSource(); }

    @Bean
    public AccountRepository repo() {
        return new JpaAccountRepository(dataSource()); // SAME singleton
    }
}

What should a senior engineer know?

  • Meta-annotations. You can create custom stereotypes by meta-annotating with @Component. For example, @Gateway annotated with @Component auto-registers the class and communicates intent.
  • Exception translation details. PersistenceExceptionTranslationPostProcessor uses PersistenceExceptionTranslator implementations. JPA, JDBC, and MongoDB each have their own translator. The translation happens at the proxy boundary.
  • proxyBeanMethods in @Configuration. Spring Boot 2.2+ supports @Configuration(proxyBeanMethods = false) for lite mode in configuration classes, avoiding the CGLIB proxy overhead when inter-bean method calls are not needed.
  • Component scanning performance. Scanning large packages is slow. Narrow @ComponentScan to specific base packages. Spring Boot 3 AOT generates code to replace scanning.
  • Interface-based repositories. Spring Data JPA creates proxy implementations of repository interfaces at runtime. You annotate the interface, not the implementation, because there is no implementation class to annotate.

What are common interview follow-up questions?

  • What happens if you create a @Service bean with new? (@Transactional does nothing, @Autowired fields are null.)
  • Is there a behavioral difference between @Component and @Service? (No, @Service is semantic only.)
  • What does @Repository add over @Component? (Exception translation via PersistenceExceptionTranslationPostProcessor.)
  • When do you use @Bean instead of @Component? (For third-party classes you cannot annotate.)
  • What is the difference between @Configuration and @Component for @Bean methods? (Full mode vs lite mode for singleton semantics.)

Visual Overview

DIAGRAM
  Stereotype Hierarchy:

  ┌──────────────────────────┐
  │       @Component          │
  │   (generic stereotype)    │
  │                           │
  │  ┌─────────┐ ┌─────────┐ │
  │  │@Service  │ │@Reposit.│ │
  │  │(semantic)│ │(+except.)│ │
  │  └─────────┘ └─────────┘ │
  │  ┌─────────┐ ┌──────────┐│
  │  │@Controll│ │@RestContr││
  │  │(+MVC)   │ │(+JSON)   ││
  │  └─────────┘ └──────────┘│
  └──────────────────────────┘

  Spring Bean vs new Object:

  ┌─────────────────────────────┐
  │  Spring Bean (container)     │
  │  ✓ DI (fields injected)     │
  │  ✓ AOP proxy (@Transactional)│
  │  ✓ @PostConstruct runs      │
  │  ✓ Scope management          │
  └─────────────────────────────┘

  ┌─────────────────────────────┐
  │  new Object()                │
  │  ✗ DI (fields are null)     │
  │  ✗ AOP proxy (no proxy)     │
  │  ✗ @PostConstruct (skipped) │
  │  ✗ Scope (none)             │
  └─────────────────────────────┘

Key Takeaway

A Spring-managed bean has its entire lifecycle controlled by the IoC container: dependency injection resolves fields and constructor parameters, AOP proxy creation enables annotations like @Transactional and @Cacheable, @PostConstruct runs initialization logic, and @PreDestroy handles cleanup. An object created with the new keyword has none of these. Creating a Spring bean manually with new is the single most common Spring mistake because @Transactional silently does nothing, @PostConstruct never runs, and @Autowired fields remain null, leading to a NullPointerException at runtime.

Spring provides four stereotype annotations. @Component is the generic base that marks any class for component scanning. @Service marks business logic and is purely semantic with no extra behavior. @Repository marks data access and adds persistence-exception translation through PersistenceExceptionTranslationPostProcessor, which translates vendor-specific exceptions (Hibernate ConstraintViolationException, for example) into Spring's portable DataAccessException hierarchy. This means Rajesh's service layer at National Bank of India can catch DuplicateKeyException regardless of whether the backend is JPA, JDBC, or MongoDB. @Controller enables MVC request handling, and @RestController adds @ResponseBody for JSON APIs.

For third-party classes like ObjectMapper, HikariDataSource, and RestTemplate that you cannot annotate with @Component, use @Bean on a factory method inside a @Configuration class. The @Configuration annotation ensures full CGLIB proxy mode, where inter-bean method calls return the same singleton instance. If you use @Component instead of @Configuration, each call creates a new instance, which is a subtle and dangerous bug with DataSource beans.

Remember one sentence: never create a Spring bean with new, because @Transactional, @Autowired, and @PostConstruct only work on container-managed beans.

---

Share
Use ← → arrow keys to navigate