Spring Core Interview Questions and Answers (2026)

  • Last Updated: April 27, 2026
  • By: javahandson
  • Series
img

Spring Core Interview Questions and Answers (2026)

Looking for Spring Core interview questions? This guide covers 15 in-depth Spring Core interview questions and answers on IoC, Dependency Injection types, ApplicationContext vs BeanFactory, @Component vs @Bean, Component Scanning internals, @Qualifier vs @Primary, Circular Dependency and @Profile — each with working code examples and interview insights for 2026.

Q1. What is the Spring Framework, and why is it used?

Answer – The Spring Framework is an open-source, lightweight application framework for the Java platform. It was created by Rod Johnson and first released in 2003 as a response to the excessive complexity of J2EE (Java 2 Enterprise Edition) development at the time. The central promise of Spring is simple: let developers focus on business logic and delegate all infrastructure concerns — object creation, wiring, lifecycle management, transaction handling, security, and more — to the framework itself.

At its heart, Spring provides two foundational capabilities. First, it implements the Inversion of Control (IoC) principle through a container that manages object creation and their dependencies. Second, it uses Dependency Injection (DI) as the mechanism to supply those dependencies. These two concepts, which are explained in depth in subsequent questions, form the backbone of everything Spring does.

Why developers and companies use Spring

Spring is not used simply because it is popular. It solves real, recurring problems in enterprise Java development:

  • Eliminates boilerplate: Before Spring, connecting a database, managing transactions, and handling exceptions required writing the same ceremonial code in every project. Spring’s abstractions — JdbcTemplate, @Transactional, exception translators — remove that repetition.
  • Promotes loose coupling: Because Spring wires objects together, each class only knows about its direct dependency interface, not the concrete implementation. This makes individual classes easier to test and replace.
  • Testability: Because Spring promotes constructor injection and interface-based design, writing unit tests with mocks becomes straightforward. You do not need a running container to test a service class.
  • Vast ecosystem: Spring’s ecosystem includes Spring Boot, Spring Security, Spring Data, Spring Batch, Spring Cloud, and more. Once you understand Spring Core, the rest of the ecosystem follows a consistent model.
  • Production readiness: Spring Boot builds on Spring to add auto-configuration, embedded servers, and health monitoring, making it a first-choice framework for building production microservices.

A quick analogy

Think of Spring as a factory manager. Without Spring, every class is responsible for creating its own tools (dependencies). With Spring, the factory manager (the IoC container) creates all the tools, organises them, and hands each worker exactly what they need. The worker just focuses on the job.

Q2. What is Inversion of Control (IoC)?

Answer – Inversion of Control (IoC) is a design principle, not a Spring-specific feature. The term describes the inversion of the flow of control in a program. In traditional programming, your application code calls a library to perform tasks — the application is in control. IoC reverses this: a framework or container takes control and calls your code at the right time.

In the context of object-oriented design, IoC specifically refers to inverting who is responsible for creating and managing an object’s dependencies. Without IoC, a class creates its own dependencies internally. With IoC, those dependencies are provided externally by a container or framework.

Without IoC — tight coupling

public class OrderService {

    // OrderService creates its own dependency — tightly coupled
    private PaymentService paymentService = new PaymentService();

    public void placeOrder(Order order) {
        paymentService.charge(order.getAmount());
    }
}

The problem here is clear. OrderService directly creates a PaymentService. If you want to use a different payment strategy in tests (say, a mock), you cannot — the concrete class is hardcoded inside the constructor.

With IoC — loose coupling

public class OrderService {

    // The dependency is provided externally — not created here
    private final PaymentService paymentService;

    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void placeOrder(Order order) {
        paymentService.charge(order.getAmount());
    }
}

Now OrderService does not know or care whether PaymentService is a real implementation or a mock. The caller — in a Spring application, the IoC container — decides what to inject. This is IoC in action.

The IoC Container in Spring

Spring implements IoC through its container. The container reads your configuration (annotations, XML, or Java config), creates all the objects (called beans) your application needs, injects their dependencies, and manages their lifecycle. You describe what you need; Spring figures out how to wire everything together.

Q3. What is Dependency Injection (DI)?

Answer – Dependency Injection is the concrete mechanism Spring uses to implement IoC. The idea is straightforward: instead of a class creating the objects it depends on, those objects (dependencies) are injected into it from the outside. Spring’s IoC container is responsible for performing that injection at application startup.

The term ‘injection’ simply means the container passes the dependency into the class — either through the constructor, a setter method, or directly into a field. The class itself has no knowledge of how the dependency was created, where it came from, or what its lifecycle is. This separation of concerns is the core benefit of DI.

How DI works in Spring — a minimal example

@Service
public class UserService {

    private final UserRepository userRepository;

    // Spring injects UserRepository here via constructor injection
    // Autowired is optional in modern Spring when there is only one constructor
    @Autowired   
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User findById(Long id) {
        return userRepository.findById(id).orElseThrow();
    }
}

When Spring starts up, it scans for @Service, @Repository, and other stereotype annotations. It finds UserRepository, creates an instance of it, then creates an instance of UserService and passes that UserRepository instance into its constructor. The developer never writes ‘new UserRepository()’ anywhere.

Benefits of DI

  • Loose coupling: Classes depend on abstractions (interfaces), not implementations.
  • Testability: You can inject a mock or stub in tests without changing production code.
  • Maintainability: Swapping an implementation only requires changing the configuration, not every class that uses it.
  • Single Responsibility: Classes focus on business logic; the container handles wiring.

Q4. What are the 3 types of Dependency Injection — Constructor, Setter, Field?

Answer – Spring supports three mechanisms for injecting dependencies into a bean. Each has a different syntax, a different behaviour, and a different set of trade-offs.

1. Constructor Injection

The dependency is provided through the class constructor. This is the most recommended approach.

@Service
public class OrderService {

    private final PaymentService paymentService;
    private final InventoryService inventoryService;

    // Spring calls this constructor and injects both dependencies
    public OrderService(PaymentService paymentService,
                        InventoryService inventoryService) {
        this.paymentService   = paymentService;
        this.inventoryService = inventoryService;
    }
}

Notice the fields are final. Once injected, they cannot be changed. This guarantees the object is fully initialised before any method is called.

2. Setter Injection

The dependency is provided through a public setter method after the bean is instantiated.

@Service
public class ReportService {

    private EmailService emailService;

    @Autowired
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }
}

Setter injection is useful when the dependency is optional or when you need to reconfigure the bean after creation (for example, in certain testing scenarios).

3. Field Injection

The dependency is injected directly into the field via reflection, without a constructor or setter.

@Service
public class NotificationService {

    @Autowired
    private SmsService smsService;  // Spring injects this via reflection

    public void notify(String message) {
        smsService.send(message);
    }
}

Field injection is the most concise syntax, which is why many developers use it. However, it comes with significant drawbacks that are discussed in Question 5.

FeatureConstructorSetterField
Fields can be finalYesNoNo
Null safety at startupYes (fails fast)NoNo
Circular dep detectionImmediate exceptionAllowed*Allowed*
Testability without SpringEasy — just call constructorModerateHard — needs reflection
Spring team recommendationPreferredOptional deps onlyDiscouraged

Q5. Which DI type should you prefer and why? What does the Spring team recommend?

Answer – The Spring team officially recommends constructor injection as the primary injection style, and this recommendation has been consistent since Spring 4. The reasons are concrete, not just stylistic.

Why Constructor Injection is recommended

1. Immutability: Constructor-injected fields can be declared final. This means once the object is built, its dependencies cannot be accidentally changed or set to null by another part of the code.

2. Fail-fast at startup: If a required dependency is missing, Spring throws an exception immediately when the application starts, not later when a method is called. This surfaces configuration problems early and makes debugging straightforward.

3. Testability without the container: You can instantiate the class directly in a unit test by simply calling new MyService(mockRepository). No Spring context needed. With field injection, you must either use Spring’s test support or reflection to inject mocks.

4. Clear contract: The constructor signature explicitly declares every dependency a class needs. When a class has too many constructor parameters, it is a visible code smell that the class has too many responsibilities — a useful design warning.

Why Field Injection is discouraged

Despite being the most common syntax found in tutorials, field injection has real problems in production code:

  • The injected field is private and final cannot be applied, making accidental reassignment possible.
  • You cannot instantiate the class outside a Spring context. This makes writing plain unit tests harder.
  • It hides dependencies. A class with ten @Autowired fields looks clean on the surface but is actually coupled to ten other things — the constructor version makes this obvious.
  • It can mask circular dependency issues that would be caught immediately with constructor injection.

When Setter Injection is appropriate

Setter injection is reasonable for optional dependencies — those that have a sensible default and where the absence of the dependency should not cause the bean to fail. You can combine it with @Autowired(required = false) to make a dependency truly optional.

@Service
public class AuditService {

    // This works fine without a MetricsClient
    private MetricsClient metricsClient;

    @Autowired(required = false)
    public void setMetricsClient(MetricsClient metricsClient) {
        this.metricsClient = metricsClient;
    }
}

Q6. What is @Autowired and how does Spring resolve it internally?

Answer – @Autowired is Spring’s annotation for triggering automatic dependency injection. When Spring’s container encounters a constructor, setter method, or field annotated with @Autowired, it resolves the appropriate bean and injects it. Understanding how Spring resolves the injection is more important than knowing the annotation’s syntax.

How Spring resolves @Autowired — step by step

  1. Step 1: Spring scans all registered beans to find candidates of the required type.
  2. Step 2: If exactly one bean of that type exists, Spring injects it directly.
  3. Step 3: If multiple beans of that type exist, Spring looks for a @Primary annotation on one of them. If found, that bean is injected.
  4. Step 4: If there is still ambiguity (no @Primary), Spring looks for a @Qualifier annotation on the injection point to identify the target bean by name.
  5. Step 5: If no qualifier is present, Spring attempts to match the field/parameter name to a bean name as a last resort.
  6. Step 6: If ambiguity remains after all these steps, Spring throws a NoUniqueBeanDefinitionException at startup.

Example — ambiguity resolved with @Qualifier

@Component
public class GmailNotifier implements NotificationService { ... }

@Component
public class SlackNotifier implements NotificationService { ... }

@Service
public class AlertService {

    private final NotificationService notificationService;

    // Without @Qualifier, Spring would throw NoUniqueBeanDefinitionException
    public AlertService(
            @Qualifier("slackNotifier") NotificationService notificationService) {
        this.notificationService = notificationService;
    }
}

The internal mechanism — AutowiredAnnotationBeanPostProcessor

Internally, Spring uses a BeanPostProcessor called AutowiredAnnotationBeanPostProcessor to process @Autowired annotations. This processor runs after the bean is instantiated but before it is put into service. It reflects over the class, finds annotated fields and methods, resolves the correct beans using the algorithm above, and injects them.

For constructor injection, Spring does not need @Autowired at all when there is only one constructor — the container automatically uses the single available constructor. @Autowired becomes necessary only when multiple constructors exist and you need to indicate which one Spring should use.

// @Autowired is optional when there is exactly one constructor
@Service
public class ProductService {

    private final ProductRepository productRepository;

    // Spring uses this automatically — no @Autowired needed (Spring 4.3+)
    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }
}

Q7. What is ApplicationContext? What are its main implementations?

Answer – ApplicationContext is Spring’s central interface for the IoC container. It extends BeanFactory (discussed in the next question) and adds a rich set of enterprise features on top of basic bean management. When a Spring application starts, the ApplicationContext reads your configuration, instantiates all singleton beans eagerly, wires their dependencies, and keeps them ready to serve requests.

Every Spring application — whether it is a standalone Java program, a web application, or a Spring Boot microservice — has exactly one ApplicationContext (or occasionally a hierarchy of them in advanced scenarios). It is the beating heart of the application.

What ApplicationContext provides

  • Bean factory: Creates, configures, and manages all beans in the application.
  • Dependency injection: Wires beans together based on their declared dependencies.
  • Environment abstraction: Provides access to properties and active profiles.
  • Event publication: Publishes application events to registered listeners via publishEvent().
  • Internationalisation (i18n): Supports message resolution for multiple locales via MessageSource.
  • Resource loading: Loads files and URLs via the ResourceLoader interface.
  • BeanPostProcessor and BeanFactoryPostProcessor: Automatically detects and registers them.

Main Implementations

Spring provides several concrete implementations of ApplicationContext, each suited to a different configuration style or environment:

AnnotationConfigApplicationContext

Used in standalone applications (non-web) where configuration is provided via Java @Configuration classes or @Component-scanned packages.

@Configuration
@ComponentScan("com.example")
public class AppConfig { }

// Bootstrapping the context manually in a main method
public class MainApp {
    public static void main(String[] args) {
        ApplicationContext ctx =
            new AnnotationConfigApplicationContext(AppConfig.class);

        UserService userService = ctx.getBean(UserService.class);
        userService.doSomething();
    }
}

ClassPathXmlApplicationContext

The original XML-based context. Loads bean definitions from an XML file on the classpath. Still encountered in legacy enterprise applications.

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

GenericWebApplicationContext / AnnotationConfigWebApplicationContext

Used in web applications. These implementations are aware of the Servlet API and integrate with the DispatcherServlet. In modern Spring Boot applications, you rarely create these manually — Spring Boot auto-configures them.

In Spring Boot

In a Spring Boot application you almost never instantiate ApplicationContext yourself. Spring Boot creates it internally and makes it available via the SpringApplication.run() return value if you need it.

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        // Spring Boot creates and manages the ApplicationContext
        ApplicationContext ctx = SpringApplication.run(MyApplication.class, args);

        // You can retrieve beans if needed
        UserService svc = ctx.getBean(UserService.class);
    }
}

Q8. What is the difference between BeanFactory and ApplicationContext?

Answer – BeanFactory is the root interface for Spring’s IoC container. It provides the basic contract for managing beans: primarily getBean() to retrieve a bean by name or type. ApplicationContext extends BeanFactory and adds a significant layer of enterprise features on top of it.

In modern Spring development, you will almost never use a BeanFactory directly. It exists as a foundational abstraction that other containers build upon. The practical distinction matters primarily for understanding when each is appropriate and for answering interview questions that probe your knowledge of the container hierarchy.

FeatureBeanFactoryApplicationContext
Bean initialisationLazy — on first getBean() callEager — at container startup
Annotation processingNot automaticAutomatic (BeanPostProcessors)
@Autowired supportNot built-inBuilt-in
Event publishingNot supportedSupported via ApplicationEvent
i18n / MessageSourceNot supportedSupported
Environment / ProfilesNot supportedSupported
AOP auto-proxyNot automaticAutomatic
Resource loadingBasicRich ResourceLoader support
Use in productionRarely — resource-constrained envsAlmost always

When would you ever use BeanFactory?

BeanFactory’s lazy initialisation means beans are only created when first requested. This is useful in resource-constrained environments (embedded devices, for example) where you need to minimise startup memory usage. In practice, even Spring Boot applications for IoT devices use ApplicationContext — the lazy loading benefit rarely outweighs the lost features.

// Using DefaultListableBeanFactory directly (very rare in practice)
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new ClassPathResource("beans.xml"));

// Bean is NOT created yet — BeanFactory is lazy
MyService service = factory.getBean(MyService.class);  // created here

// Using ApplicationContext — all singletons already created at this point
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService service2 = ctx.getBean(MyService.class);  // already exists

Q9. What is @Component vs @Bean vs @Configuration — when to use which?

Answer – These three annotations all result in Spring-managed beans, but they serve different purposes and apply in different contexts. Confusing them is one of the most common mistakes in Spring code.

@Component — for your own classes

@Component is a class-level annotation that marks a class as a Spring-managed bean. Spring detects it during component scanning and creates an instance automatically. You use @Component (and its specialisations @Service, @Repository, @Controller) on classes that you own — classes in your codebase.

@Component  // Spring will create and manage this bean
public class EmailValidator {

    public boolean isValid(String email) {
        return email != null && email.contains("@");
    }
}

@Service, @Repository, and @Controller are specialisations of @Component. They add semantic meaning (service layer, data layer, web layer) and some Spring behaviour — @Repository, for example, enables Spring’s exception translation for database errors.

@Bean — for third-party or programmatic beans

@Bean is a method-level annotation used inside a @Configuration class. You use it when you need to register a bean that you cannot annotate directly with @Component — typically because it is a class from a third-party library, or because its creation requires specific configuration logic.

@Configuration
public class AppConfig {

    // You cannot annotate ObjectMapper with @Component
    // because it's a Jackson library class
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return mapper;
    }

    // Custom DataSource with specific pool settings
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://localhost/mydb");
        config.setMaximumPoolSize(20);
        return new HikariDataSource(config);
    }
}

@Configuration — for configuration classes

@Configuration marks a class as a source of bean definitions. It tells Spring that this class contains @Bean methods that should be processed. Without @Configuration, a class with @Bean methods still works, but with an important behavioural difference.

@Configuration  // CGLIB-proxied — inter-bean method calls are intercepted
public class DatabaseConfig {

    @Bean
    public DataSource dataSource() { ... }

    @Bean
    public EntityManagerFactory entityManagerFactory() {
        // Calling dataSource() here returns the SAME Spring bean
        // because @Configuration enables CGLIB proxy
        return new LocalContainerEntityManagerFactoryBean(dataSource());
    }
}

Without @Configuration (using @Component instead), the dataSource() call inside entityManagerFactory() would create a brand new DataSource each time — not the Spring-managed singleton.

AnnotationApplied toUse whenComponent scan?
@ComponentClassYour own class that should be a beanYes
@BeanMethodThird-party class or complex construction logicNo — inside @Configuration
@ConfigurationClassDeclaring multiple @Bean methods as a unitYes

Q10. What is Component Scanning and how does it work internally?

Answer – Component scanning is the mechanism by which Spring automatically discovers and registers beans from your classpath, without you having to declare each one manually in a configuration file. When component scanning is enabled, Spring scans the packages you specify, finds all classes annotated with @Component (or its specialisations), and registers them as bean definitions in the container.

How to enable component scanning

// Option 1: Java configuration
@Configuration
@ComponentScan(basePackages = "com.example.myapp")
public class AppConfig { }

// Option 2: Spring Boot — @SpringBootApplication includes @ComponentScan
// It scans the package of the annotated class and all sub-packages
@SpringBootApplication  // includes @ComponentScan
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

How it works internally — step by step

  1. Step 1: Spring’s ClassPathBeanDefinitionScanner is invoked with the base packages to scan.
  2. Step 2: The scanner uses ASM (bytecode reading library) to read class files from the classpath. It does NOT load every class into the JVM at this point — it reads bytecode metadata without full class loading, which keeps startup efficient.
  3. Step 3: For each class file, the scanner checks for the presence of @Component (or meta-annotations composed with @Component, like @Service or @Repository).
  4. Step 4: For matching classes, the scanner creates a BeanDefinition object — a metadata descriptor that holds the class name, scope, lazy-init flag, constructor arguments, and other configuration. The bean itself is NOT yet instantiated.
  5. Step 5: BeanDefinitions are registered in the BeanDefinitionRegistry (backed by DefaultListableBeanFactory).
  6. Step 6: After all definitions are registered, BeanFactoryPostProcessors run (e.g., PropertySourcesPlaceholderConfigurer to resolve @Value expressions).
  7. Step 7: Finally, all singleton beans are instantiated and their dependencies are injected.

Filtering component scanning

You can include or exclude specific classes or annotations from scanning:

@ComponentScan(
    basePackages = "com.example",
    excludeFilters = @ComponentScan.Filter(
        type = FilterType.ANNOTATION,
        classes = Repository.class  // exclude all @Repository beans
    ),
    includeFilters = @ComponentScan.Filter(
        type = FilterType.ASSIGNABLE_TYPE,
        classes = SpecialService.class
    )
)
public class AppConfig { }

Q11. What is @Qualifier vs @Primary — difference and when to use each?

Answer – Both @Qualifier and @Primary exist to resolve the same problem: when Spring finds multiple beans of the same type, it cannot decide which one to inject and throws a NoUniqueBeanDefinitionException. These two annotations provide different ways to resolve that ambiguity.

@Primary — the default choice

@Primary marks one bean as the default candidate when multiple beans of the same type are available. You use @Primary when one implementation should be the standard choice and the others are exceptions or alternatives.

public interface PaymentGateway {
    void process(double amount);
}

@Component
@Primary  // This is the default choice when PaymentGateway is injected
public class StripeGateway implements PaymentGateway { ... }

@Component
public class PayPalGateway implements PaymentGateway { ... }

@Service
public class CheckoutService {

    private final PaymentGateway paymentGateway;

    // Spring injects StripeGateway because it is @Primary
    public CheckoutService(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }
}

@Qualifier — explicit selection

@Qualifier allows you to specify exactly which bean you want by its name or qualifier value. You use @Qualifier at the injection point to override the default resolution. This is more explicit and more granular than @Primary.

@Service
public class RefundService {

    private final PaymentGateway stripeGateway;
    private final PaymentGateway payPalGateway;

    public RefundService(
            @Qualifier("stripeGateway") PaymentGateway stripeGateway,
            @Qualifier("payPalGateway") PaymentGateway payPalGateway) {
        this.stripeGateway  = stripeGateway;
        this.payPalGateway  = payPalGateway;
    }
}

How they interact

If both @Primary and @Qualifier are involved: @Qualifier always wins. If the injection point has a @Qualifier, Spring uses it, regardless of whether one of the candidates is @Primary.

Aspect@Primary@Qualifier
Where to placeOn the bean definitionAt the injection point
GranularityApplication-wide defaultPer-injection-point control
Best forOne obvious default choiceInjecting specific implementations
When both presentOverridden by @QualifierTakes priority

Q12. What is Circular Dependency in Spring — how does Spring detect it?

Answer

A circular dependency occurs when two or more beans depend on each other, forming a cycle. For example: Bean A requires Bean B, and Bean B requires Bean A. When Spring tries to create A, it starts creating B, which needs A — but A is not finished yet. This creates a deadlock in the instantiation process.

Example of a circular dependency

@Service
public class ServiceA {

    private final ServiceB serviceB;

    public ServiceA(ServiceB serviceB) {  // A depends on B
        this.serviceB = serviceB;
    }
}

@Service
public class ServiceB {

    private final ServiceA serviceA;

    public ServiceB(ServiceA serviceA) {  // B depends on A — circular!
        this.serviceA = serviceA;
    }
}

How Spring detects and handles circular dependencies

The behaviour depends on the type of injection being used:

  • With constructor injection: Constructor injection: Spring detects the cycle immediately and throws BeanCurrentlyInCreationException at startup. This is the desired behaviour — it surfaces the design problem early.
  • With setter/field injection: Setter or field injection: Spring can partially resolve cycles using its 3-level bean cache (singletonObjects, earlySingletonObjects, singletonFactories). It injects an early reference to an incompletely initialised bean. The cycle is ‘resolved’ but the object graph is in an inconsistent state during initialisation.
// This will FAIL at startup with constructor injection:
// BeanCurrentlyInCreationException: Error creating bean 'serviceA':
// Requested bean is currently in creation: Is there an unresolvable circular reference?

// Spring Boot 2.6+ also detects setter/field circular deps by default
// and throws an exception instead of silently allowing them.
// You can re-enable the old behaviour with:
// spring.main.allow-circular-references=true (not recommended)

The right fix: refactor the design

A circular dependency is almost always a sign of a design problem — two classes are too tightly coupled to each other. The solutions include:

  1. Extract the shared logic into a third class that both A and B depend on.
  2. Use an event-based design where one service publishes an event and the other listens, removing the direct dependency.
  3. Use @Lazy on one of the constructor parameters to break the cycle — Spring injects a proxy first and resolves the real bean later.
// Using @Lazy to break a circular dependency (use sparingly)
@Service
public class ServiceA {

    private final ServiceB serviceB;

    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

Q13. What is @Lazy and when would you use it?

Answer – By default, Spring creates all singleton beans eagerly at application startup. @Lazy is an annotation that changes this behaviour for a specific bean — it tells Spring to defer the creation of that bean until the first time it is requested (via getBean() or through injection).

Usage on a bean definition

@Component
@Lazy  // This bean will NOT be created at startup
public class HeavyReportGenerator {

    public HeavyReportGenerator() {
        // Imagine this takes 5 seconds and allocates significant memory
        System.out.println("HeavyReportGenerator initialised");
    }
}

If no code ever requests this bean during the application’s lifetime, it is never created. This can improve startup time for applications with beans that are rarely or never used.

Usage at the injection point

You can also apply @Lazy at the injection point, which injects a CGLIB proxy instead of the actual bean. The real bean is created only when a method is first called on that proxy.

@Service
public class DashboardService {

    // A proxy is injected at startup.
    // The real HeavyReportGenerator is created only on first method call.
    @Autowired
    @Lazy
    private HeavyReportGenerator reportGenerator;

    public void generateReport() {
        // HeavyReportGenerator is instantiated HERE, on first use
        reportGenerator.generate();
    }
}

When to use @Lazy

  • Startup performance: A bean takes a long time to initialise (database connections, file loading) and is only needed under specific conditions.
  • Optional features: A component is only active in certain environments or under certain load conditions.
  • Breaking circular dependencies: As a last resort (see Q12), @Lazy can inject a proxy to break a cycle.
  • Large contexts in tests: Lazily loading beans in integration tests where you only need a subset of the application context.

Caution with @Lazy

Overusing @Lazy can hide startup problems. If a bean fails to initialise (e.g., because its configuration is wrong), you will discover that error the first time a user triggers that code path — not at startup when you can catch it immediately. Use @Lazy deliberately, not as a default.

Q14. What is the difference between XML, Annotation and Java-based configuration?

Answer – Spring has supported three distinct styles of configuration throughout its evolution. Each was the preferred approach in its era, and each still works in modern Spring — though Java-based configuration is now the standard in new projects.

XML-based configuration (Spring 1.x era)

The original Spring approach. All bean definitions are declared in an XML file. Verbose and explicit, but very clear about what is being configured.

<!-- applicationContext.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="...">

    <bean id="userRepository" class="com.example.UserRepositoryImpl"/>

    <bean id="userService" class="com.example.UserService">
        <constructor-arg ref="userRepository"/>
    </bean>

</beans>

Annotation-based configuration (Spring 2.5+)

Spring 2.5 introduced @Component, @Autowired, and @Service, allowing bean definitions and injection to be declared directly in the Java source code. XML was still needed to enable component scanning.

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;
}

// Still needed some XML or Java config to enable scanning:
// <context:component-scan base-package="com.example"/>

Java-based configuration (Spring 3.0+)

@Configuration and @Bean eliminated the need for XML entirely. Configuration is expressed as plain Java code, which is type-safe, refactorable, and IDE-friendly.

@Configuration
@ComponentScan("com.example")
@PropertySource("classpath:application.properties")
public class AppConfig {

    @Bean
    public DataSource dataSource(
            @Value("${db.url}") String url,
            @Value("${db.username}") String username) {
        HikariConfig cfg = new HikariConfig();
        cfg.setJdbcUrl(url);
        cfg.setUsername(username);
        return new HikariDataSource(cfg);
    }
}
AspectXMLAnnotationJava Config
Type safetyNonePartialFull
Refactoring supportNone (strings)GoodExcellent
VerbosityHighLowMedium
Visibility of wiringCentralised in XMLScattered in classesCentralised in @Config
Third-party beansYes (in XML)NoYes (via @Bean)
Modern Spring BootNot preferredYes (@Component etc.)Preferred for config

In practice, modern Spring Boot applications use a combination: @Component and its specialisations for application classes, @Configuration + @Bean for infrastructure beans (DataSource, RestTemplate, etc.), and application.properties / application.yml for externalised values.

Q15. What is @Profile — how does environment-based bean registration work?

Answer – @Profile is Spring’s mechanism for environment-based conditional bean registration. It allows you to define beans that should only be active in certain environments — for example, a bean that connects to a real database in production but uses an in-memory database in development, or a mock email service in tests.

A profile is simply a named grouping. You assign beans to profiles, and then at application startup you tell Spring which profile(s) are active. Spring only registers beans that belong to the active profile (or no profile at all — unprofile beans are always registered).

Defining beans with @Profile

// Only active in 'production' environment
@Configuration
@Profile("production")
public class ProductionDataSourceConfig {

    @Bean
    public DataSource dataSource() {
        // Connect to real PostgreSQL database
        HikariConfig cfg = new HikariConfig();
        cfg.setJdbcUrl("jdbc:postgresql://prod-db:5432/appdb");
        return new HikariDataSource(cfg);
    }
}

// Only active in 'development' or 'test' environment
@Configuration
@Profile({"development", "test"})
public class DevDataSourceConfig {

    @Bean
    public DataSource dataSource() {
        // In-memory H2 database — no external dependency
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build();
    }
}

Activating profiles

Profiles can be activated in several ways:

# Option 1: application.properties or application.yml
spring.profiles.active=production

# Option 2: Environment variable (useful in Docker/Kubernetes)
SPRING_PROFILES_ACTIVE=production

# Option 3: JVM system property
# java -Dspring.profiles.active=production -jar myapp.jar

# Option 4: Programmatic (useful in tests)
// @ActiveProfiles("test") on your test class

Profile negation and default profile

// Active in every profile EXCEPT 'production'
@Component
@Profile("!production")
public class MockEmailService implements EmailService { ... }

// Active when no specific profile is activated
@Component
@Profile("default")
public class LocalDevelopmentSetup { ... }

Combining @Profile with @Bean

@Profile can also be applied directly to individual @Bean methods inside a @Configuration class:

@Configuration
public class MessagingConfig {

    @Bean
    @Profile("production")
    public MessageBroker kafkaBroker() {
        return new KafkaMessageBroker("kafka:9092");
    }

    @Bean
    @Profile("!production")
    public MessageBroker inMemoryBroker() {
        return new InMemoryMessageBroker();
    }
}

Leave a Comment