Spring Beans Demystified — Scopes, Lifecycle, and Lazy Injection in a Credit Card Eligibility Microservice
💡 Introduction
Working on a credit card eligibility microservice recently taught me a lot about Spring’s bean system. I had to make design choices that ensured thread-safety, clean state management, and efficient memory use — especially when dealing with stateful evaluations per user.
In this post, I’ll walk through:
What Spring beans are
Their lifecycle
Different scopes
Lazy injection and why it’s a lifesaver
Real examples from a credit card eligibility context
☕ What is a Spring Bean?
A bean in Spring is simply an object that is managed by the Spring container. When you annotate a class with @Component, @Service, or define it using @Bean, Spring takes care of:
Instantiating it
Injecting its dependencies
Managing its lifecycle
Example:
@Component
public class CreditScoreService {
public int getScore(String userId) {
// Dummy logic
return 720;
}
}
Spring creates and manages a CreditScoreService bean.
🔄 Bean Lifecycle (Simplified)
Instantiation – Spring creates the object.
Dependency Injection – Injects all required fields/constructors.
PostConstruct – Runs any method annotated with
@PostConstruct.Bean is ready to use.
PreDestroy – If it's a singleton and being destroyed,
@PreDestroymethods are called.
@Component
public class EligibilityLogger {
@PostConstruct
public void init() {
System.out.println("Logger initialized");
}
@PreDestroy
public void destroy() {
System.out.println("Cleaning up logger");
}
}
📦 Bean Scopes — Singleton vs Prototype
🔹 Singleton (default)
Only one instance is created per Spring context.
Shared across the entire application.
Example:
@Service
public class EligibilityService {
// Singleton by default
}
✅ Good for stateless services, utilities, or shared components.
🔹 Prototype
A new instance is created every time the bean is requested.
Ideal when the bean holds request-specific data or state.
🧪 Example from Credit Card Eligibility
Let’s say we have a service that evaluates whether a user is eligible for a credit card based on credit score and income.
✅ Step 1: Define a stateful bean
@Component
@Scope("prototype")
public class EligibilityEvaluationContext {
private boolean eligible;
private String rejectionReason;
private Map<String, Object> debugInfo = new HashMap<>();
// Getters & Setters
}
Why prototype? Because this context is specific to each request and shouldn't be shared.
✅ Step 2: Inject it using ObjectFactory (Lazy Injection)
@Service
public class EligibilityService {
@Autowired
private CreditScoreService scoreService;
@Autowired
private ObjectFactory<EligibilityEvaluationContext> contextFactory;
public EligibilityEvaluationContext evaluate(String userId) {
int score = scoreService.getScore(userId);
EligibilityEvaluationContext context = contextFactory.getObject();
if (score < 650) {
context.setEligible(false);
context.setRejectionReason("Low credit score");
context.getDebugInfo().put("score", score);
} else {
context.setEligible(true);
}
return context;
}
}
👉 ObjectFactory.getObject() ensures we get a fresh EligibilityEvaluationContext every time — this avoids bugs due to shared state and keeps our service thread-safe.
⚠️ Why Not Inject Prototype Directly?
@Autowired
private EligibilityEvaluationContext context;
This would only create one instance — during startup — and reuse it everywhere. Totally defeats the point of @Scope("prototype").
🧊 Bonus: Lazy Initialization with @Lazy
If a bean is expensive to create and might not be used right away, you can delay its creation:
@Autowired
@Lazy
private ExternalScoringClient scoringClient;
Now, scoringClient will only be created when it's first accessed — not during app startup.
🧠 Key Takeaways
Use Singleton for stateless services and utilities.
Use Prototype for stateful, request-specific objects.
Use
ObjectFactoryorProviderto inject prototype beans into singleton services safely.Use
@Lazyto defer expensive bean creation until needed.
📌 Conclusion
Understanding how Spring manages beans is crucial for designing clean, safe microservices — especially in user-facing financial applications like credit card approvals.
If you're ever storing per-user evaluation data or debugging context, don’t rely on singletons. Prototype + lazy injection is your friend.