Discovery boundary
Component scanning starts at the application package.
With OrdersApplication in com.example.orders, the default scan searches that package and every subpackage. It finds classes such as:
com.example.orders.notification.EmailNotificationSender
com.example.orders.service.OrderService
com.example.orders.configuration.TimeConfiguration
com.example.orders.runner.DemoRunnerIt does not automatically scan a sibling such as com.example.payments. Put the application class at a sensible package root instead of widening the scan without a reason.
Scanning reads class metadata. It is looking for component stereotypes and configuration classes, not for every Java class in the package. An unannotated DTO in the same folder is visible to Java but does not become a bean merely because the scanner passed over it.
package com.example.orders;
@Service
class OrderService { }
// registered by component scanning
class CreateOrderRequest { }
// ordinary class; no bean definition is registeredTwo creation paths
Use scanning for your components and @Bean for explicit construction.
Stereotype annotation
Use @Component, @Service or @Repository when you own the class and its role belongs in the application.
@Bean factory method
Use a method in @Configuration when the class comes from a library, construction needs explicit values, or the application should own the creation policy.
@Configuration(proxyBeanMethods = false)
public class TimeConfiguration {
@Bean
public Clock applicationClock() {
return Clock.systemUTC();
}
@Bean
public OrderTimestampService orderTimestampService(
Clock applicationClock
) {
return new OrderTimestampService(applicationClock);
}
}The default bean names here are applicationClock and orderTimestampService, taken from the method names. Spring resolves the Clock parameter before invoking the second method.
@Configuration is itself a component stereotype, so the scan finds TimeConfiguration. Spring then processes its @Bean methods as additional definitions.
- 01
Find TimeConfiguration
The default component scan discovers the configuration class.
- 02
Register both method definitions
The declared return types tell Spring that a
Clockand anOrderTimestampServicecan be created. - 03
Resolve method parameters
Spring finds the managed
Clockbefore calling the service factory method. - 04
Register returned objects as beans
The object returned from each method is managed by the context.
Choosing the creation path
The decision is about ownership and construction, not class importance.
| Object | Likely definition style | Reason |
|---|---|---|
OrderService | @Service | You own the application class and its service role is clear. |
Clock | @Bean | It is a JDK class and cannot be edited to add a Spring annotation. |
| Vendor SDK client | @Bean | Construction needs credentials, endpoints or a builder you control. |
CreateOrderRequest | Neither | Each request is ordinary input data, not a shared collaborator. |
Avoid defining the same application service both with @Service and an unrelated @Bean method unless two distinct definitions are intentional. Otherwise the context may contain two candidates of the same type and injection becomes ambiguous.
@Service
class OrderService { }
@Bean
OrderService orderService() {
return new OrderService();
}
// These are two bean definitions, not two annotations
// describing one shared instance.proxyBeanMethods
With proxies disabled, direct method calls are ordinary Java calls.
@Configuration(proxyBeanMethods = false) avoids method interception. It is efficient and clear when bean methods are independent or receive other beans as parameters.
// Correct: Spring supplies its managed TokenGenerator bean.
@Bean
public OtpService otpService(TokenGenerator tokenGenerator) {
return new OtpService(tokenGenerator);
}
// Risky with proxyBeanMethods = false:
@Bean
public OtpService anotherOtpService() {
return new OtpService(tokenGenerator());
// tokenGenerator() is a normal Java call and creates another instance.
}The default @Configuration mode uses a proxy that can intercept calls between @Bean methods and return the managed bean. Parameter injection works in either mode and states the dependency more directly.
proxyBeanMethods = true
Inter-bean calls are intercepted
A direct call such as tokenGenerator()can be redirected to the context's managed singleton.
proxyBeanMethods = false
Methods have normal Java semantics
Calling tokenGenerator() executes its body and constructs whatever that method normally returns.
@Configuration(proxyBeanMethods = false)
class TokenConfiguration {
@Bean
TokenGenerator tokenGenerator() {
return new TokenGenerator();
}
@Bean
OtpService otpService() {
return new OtpService(tokenGenerator());
}
}
// Context bean A: created from tokenGenerator()
// Plain object B: created when otpService() calls tokenGenerator()
// OtpService receives B, not the managed bean A.@Bean
OtpService otpService(TokenGenerator tokenGenerator) {
return new OtpService(tokenGenerator);
}Managed and ordinary objects
Spring should manage services and infrastructure, not every object.
Spring-managed objects participate in injection, scopes and lifecycle callbacks. Short-lived domain values—request DTOs, entities, value objects and exceptions—are normally created by application or persistence code. They do not need to be fetched from the ApplicationContext.
When a managed service needs another managed object, constructor injection is the normal path. Direct context access remains an integration escape hatch, not the default application design.
Usually managed
Collaborators and infrastructure
Services, repositories, clients, clocks, transaction infrastructure and configuration-driven strategies.
Usually ordinary
Values and per-operation state
DTOs, entities, order lines, identifiers, exceptions and local collections.
@Bean
Clock applicationClock() {
return Clock.systemUTC();
}
// The returned Clock is a bean because Spring invokes
// this definition and registers the result.
Instant timestamp = applicationClock.instant();
// The Instant is a normal value returned during business work.
// It does not become a bean.Calling new inside a @Bean method is completely normal: the returned object is precisely what Spring is being asked to manage. Calling new inside a service for one of its required collaborators bypasses injection and recreates the coupling from module 1.
Inject time
Clock turns the current time into a replaceable dependency.
Clock fixedClock = Clock.fixed(
Instant.parse("2026-08-31T00:00:00Z"),
ZoneOffset.UTC
);Clock.fixed(...) always returns the same instant. That is useful in this learning checkpoint and in tests. A production bean usually returns Clock.systemUTC(), while a test configuration supplies a fixed clock.
public class OrderTimestampService {
private final Clock clock;
public OrderTimestampService(Clock clock) {
this.clock = clock;
}
public Instant currentTimestamp() {
return clock.instant();
}
}System UTC
Clock.systemUTC()Returns the current real time in UTC.
Fixed
Clock.fixed(
instant,
ZoneOffset.UTC
)Always returns one instant, ideal for deterministic assertions.
Offset
Clock.offset(
baseClock,
Duration.ofMinutes(5)
)Returns time shifted from another clock.
Clock testClock = Clock.fixed(
Instant.parse("2026-08-31T00:00:00Z"),
ZoneOffset.UTC
);
OrderTimestampService service =
new OrderTimestampService(testClock);
assertEquals(
Instant.parse("2026-08-31T00:00:00Z"),
service.currentTimestamp()
);The service is tested as plain Java. Spring configuration decides which clock production receives; the class itself remains independent of the container.
When direct context access is justified
Use ApplicationContext at integration boundaries, not as hidden injection.
| Situation | Normal choice | Why |
|---|---|---|
| A service always needs one repository | Constructor injection | The dependency is stable and known when the service is created. |
| Bootstrapping framework integration | Context access may be reasonable | The boundary's job may be to connect Spring with external lifecycle code. |
| Runtime plugin chosen from an open-ended set | Dynamic lookup may be reasonable | The exact implementation may genuinely be unknown at construction time. |
A method hides getBean for convenience | Avoid it | It hides dependencies and couples ordinary business code to Spring. |
Even dynamic selection often has a cleaner injectable form, such as injecting a map of strategies into a dedicated registry. Reach for the context only when the boundary truly needs container-level behaviour.
Practice checkpoint
Write it before reading the solution.
Add deterministic timestamps to the existing non-web application.
- Create an
OrderTimestampServicethat depends onClock. - Define a fixed UTC clock and the service in explicit configuration.
- Keep
proxyBeanMethods = false. - Print the timestamp from the existing runner.
Hints, in increasing order
OrderTimestampServiceneeds imports for bothClockandInstant.- Create the clock and service through two
@Beanmethods. - Inject
Clockas a parameter to the service bean method; do not callapplicationClock(). - Add
OrderTimestampServiceto all three places in the runner constructor: parameter, assignment and field.
Working solution
package com.example.orders.time;
import java.time.Clock;
import java.time.Instant;
public class OrderTimestampService {
private final Clock applicationClock;
public OrderTimestampService(Clock applicationClock) {
this.applicationClock = applicationClock;
}
public Instant currentTimestamp() {
return applicationClock.instant();
}
}package com.example.orders.configuration;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.example.orders.time.OrderTimestampService;
@Configuration(proxyBeanMethods = false)
public class TimeConfiguration {
@Bean
public Clock applicationClock() {
return Clock.fixed(
Instant.parse("2026-08-31T00:00:00Z"),
ZoneOffset.UTC
);
}
@Bean
public OrderTimestampService orderTimestampService(
Clock applicationClock
) {
return new OrderTimestampService(applicationClock);
}
}package com.example.orders.runner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import com.example.orders.service.OrderService;
import com.example.orders.service.OtpService;
import com.example.orders.time.OrderTimestampService;
@Component
public class DemoRunner implements CommandLineRunner {
private final OrderService orderService;
private final OtpService otpService;
private final OrderTimestampService orderTimestampService;
public DemoRunner(
OrderService orderService,
OtpService otpService,
OrderTimestampService orderTimestampService
) {
this.orderService = orderService;
this.otpService = otpService;
this.orderTimestampService = orderTimestampService;
}
@Override
public void run(String... args) {
orderService.placeOrder("ORD-201", "customer@example.com");
otpService.sendOtp("+919876543210", "482731");
System.out.println(
"TIMESTAMP: " + orderTimestampService.currentTimestamp()
);
}
}TIMESTAMP: 2026-08-31T00:00:00ZElapsed time · hint level (H0 means no hint; H1–Hn is the last hint used) · conceptual errors · mechanical errors · explanation confidence from 0–2.
Foundation check
Who owns each object?
Spring owns the configured Clock and OrderTimestampService beans. The Instant returned by the service is an ordinary value object created by the clock; it is not a bean.