Beans and @Component

Mark your classes with @Component or a stereotype so component scanning registers them as beans, and know where scanning looks.

Syntax@Component | @Service | @Repository | @RestController class Name { Name(Dependency d) { ... } }

A bean is any object the ApplicationContext creates and manages. The most common way to declare one is to put @Component on a class. At startup, component scanning finds annotated classes, registers a bean definition for each and creates one instance.

The stereotypes

@Service, @Repository, @Controller and @RestController are all meta-annotated with @Component, so scanning picks them up too. They tell readers what layer a class belongs to, and two of them change behaviour:

  • @Repository makes the class eligible for exception translation, turning vendor-specific persistence exceptions into Spring's DataAccessException hierarchy when a translator is present.
  • @RestController registers the class with Spring MVC and writes return values straight to the response body.

@Service adds nothing beyond @Component today, but it documents intent and is what readers expect on business logic.

Where scanning looks

@SpringBootApplication includes @ComponentScan on the package of the main class and every package below it. That is the most common source of "bean not found" errors: a class in com.example.shared is invisible to an application whose main class lives in com.example.shop. Put the main class in the root package of your code rather than adding scanBasePackages to work round a poor layout.

com.example.shop
  ShopApplication.java        // scanning starts here
  orders/OrderService.java    // found
  orders/OrderRepository.java // found
com.example.shared
  Money.java                  // not scanned

Bean names

A component's name defaults to the simple class name with the first letter lower-cased: OrderService becomes orderService. Names matter when two beans share a type and you pick one by name, and two classes with the same simple name in different packages clash at startup. Give one an explicit name with @Component("legacyOrderService").

What should be a bean

Make services, repositories, controllers, clients and anything else that holds dependencies or is shared across requests a bean. Do not annotate entities, DTOs, records you return from controllers or other value objects: they are created per request or per row, with different data each time, and they belong to new. A singleton bean is shared by every thread, so it must not keep per-request state in fields.

Scanned classes can be package-private; Spring does not need them to be public. Keeping implementation classes package-private within a feature package is a cheap way to stop other packages depending on them.

Example

Example · java

import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

record Customer(UUID id, String email, Instant createdAt) {}

@Repository
class CustomerRepository {
    private final ConcurrentMap<UUID, Customer> rows = new ConcurrentHashMap<>();

    Customer save(Customer customer) {
        rows.put(customer.id(), customer);
        return customer;
    }

    Optional<Customer> findByEmail(String email) {
        return rows.values().stream().filter(c -> c.email().equalsIgnoreCase(email)).findFirst();
    }
}

@Service
public class CustomerService {
    private final CustomerRepository repository;

    public CustomerService(CustomerRepository repository) {
        this.repository = repository;
    }

    public Customer register(String email) {
        return repository.findByEmail(email)
                .orElseGet(() -> repository.save(new Customer(UUID.randomUUID(), email, Instant.now())));
    }
}

When to use it

  • Register an order service and its repository so a controller can receive them by constructor injection.
  • Track down a NoSuchBeanDefinitionException caused by a class living outside the main class's package.
  • Turn an audit sink on per environment with a property instead of commenting code in and out.

More examples

Giving a component an explicit name

An explicit name is useful when other code refers to the bean by name, for example with @Qualifier, or when two classes with the same simple name live in different packages. Renaming the class later then does not change the bean name and break those references.

Example · java
import org.springframework.stereotype.Component;

interface ExchangeRates {
    double rate(String from, String to);
}

// Default name would be "fixedExchangeRates"; this one is shorter and stable across renames.
@Component("fixedRates")
public class FixedExchangeRates implements ExchangeRates {
    @Override
    public double rate(String from, String to) {
        return from.equals(to) ? 1.0 : 0.85;
    }
}

Registering a component only when a property is set

Conditions run while bean definitions are being read, so with app.audit.enabled unset or false the class never becomes a bean. Anything that injects AuditSink then fails at startup unless another implementation exists, which is what you want: a missing collaborator is reported before traffic arrives. Use ObjectProvider in the consumer if the sink is genuinely optional.

Example · java
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

interface AuditSink {
    void record(String event);
}

@Component
@ConditionalOnProperty(name = "app.audit.enabled", havingValue = "true")
public class LogAuditSink implements AuditSink {
    @Override
    public void record(String event) {
        System.out.println("audit: " + event);
    }
}

A layout that scanning handles without extra settings

Keep the main class at the top of your package tree and every feature below it. Scanning then finds everything with no configuration, and test slices such as @WebMvcTest, which search upwards for the main class, find it too. Value types like Order sit next to their services but carry no stereotype.

Example · text
src/main/java/com/example/shop/
  ShopApplication.java          @SpringBootApplication (scan root)
  orders/
    OrderController.java        @RestController
    OrderService.java           @Service
    OrderRepository.java        @Repository
    Order.java                  @Entity or record, not a bean
  customers/
    CustomerService.java        @Service
    CustomerRepository.java     @Repository

Discussion

  • Be the first to comment on this lesson.