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)

  1. Instantiation – Spring creates the object.

  2. Dependency Injection – Injects all required fields/constructors.

  3. PostConstruct – Runs any method annotated with @PostConstruct.

  4. Bean is ready to use.

  5. PreDestroy – If it's a singleton and being destroyed, @PreDestroy methods 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 ObjectFactory or Provider to inject prototype beans into singleton services safely.

  • Use @Lazy to 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.

0
Subscribe to my newsletter

Read articles from Shravani Thirunagari directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Shravani Thirunagari
Shravani Thirunagari

Hi there, I am a product developer primarily skilled in Java(SpringBoot), Node JS, and ES6 (React JS). I have around 7 years of experience in delivering end-to-end products. My expertise lies in implementing optimized solutions using relevant tech stacks. As part of this, I like to explore and learn new technologies. I am a curious student.