Skip to content

Bean Configuration Interview Questions & Answers

15 questions Updated 2026-06-26 Share:

Defining and configuring beans in Spring — @Configuration and @Bean methods, init/destroy callbacks, @Conditional beans, @Value and SpEL, bean scopes, and Java config versus component scanning.

Read the in-depth guideConfiguring Beans in Spring: @Bean, @Conditional, @Value, and SpEL(opens in new tab)
15 of 15

@Configuration marks a class as a source of bean definitions, and @Bean marks a method whose return value becomes a container-managed bean. Together they're Spring's Java-based configuration — the modern replacement for XML.

@Configuration
class AppConfig {
    @Bean
    PaymentGateway paymentGateway() {     // method name = bean name "paymentGateway"
        return new StripeGateway();        // returned object is managed by the container
    }
}

Use @Bean factory methods when you need explicit construction logic or when the type is a third-party class you can't annotate with @Component. The method's parameters are themselves autowired, so @Bean methods can depend on other beans. This is the standard way to configure infrastructure objects like DataSource, RestClient, or ObjectMapper.

Rule of thumb: @Configuration + @Bean = "build this object and let Spring manage it." Reach for it whenever you can't (or don't want to) annotate the class itself.

A @Bean method declares its dependencies as method parameters, and Spring autowires them from the container — exactly like constructor injection but for a factory method.

@Configuration
class ServiceConfig {
    @Bean
    OrderService orderService(PaymentGateway gateway, InventoryRepo repo) {
        // 'gateway' and 'repo' are resolved from the container and passed in:
        return new OrderService(gateway, repo);
    }
}

Each parameter is resolved by type (with @Qualifier/@Value available on parameters too). This is the preferred way for one bean to use another inside @Configuration — cleaner than calling another @Bean method directly. Because parameters are autowired, the order in which you declare @Bean methods doesn't matter; Spring sorts out the dependency graph.

Rule of thumb: Have a @Bean method take what it needs as parameters and let Spring inject them — don't call sibling @Bean methods to get collaborators.

You can hook a bean's lifecycle three ways: the @Bean(initMethod, destroyMethod) attributes, the @PostConstruct/@PreDestroy annotations, or implementing InitializingBean/DisposableBean.

@Configuration
class ResourceConfig {
    @Bean(initMethod = "start", destroyMethod = "stop")
    ConnectionPool pool() {             // start() runs after creation, stop() on shutdown
        return new ConnectionPool();
    }
}

// For your own classes, annotations are usually cleaner:
@Component
class Cache {
    @PostConstruct void warmUp()  { }   // after dependencies injected
    @PreDestroy    void flush()   { }   // before context closes
}

@Bean(initMethod=..., destroyMethod=...) is ideal for third-party classes with lifecycle methods you can't annotate. For closeable types Spring even infers a destroy method named close or shutdown automatically. @PostConstruct/@PreDestroy are best for your own code. Note: destroy callbacks don't fire for prototype-scoped beans.

Rule of thumb: Annotate your own classes with @PostConstruct/@PreDestroy; use @Bean(initMethod/destroyMethod) for library types you can't annotate.

@Value injects externalized configuration — property values, environment variables, or SpEL expressions — into fields, constructor parameters, or @Bean method parameters.

@Service
class MailService {
    @Value("${mail.host}")                       // from application.properties
    private String host;

    @Value("${mail.port:25}")                    // with a default if unset
    private int port;

    MailService(@Value("${mail.from}") String from) { } // constructor injection too
}

The ${...} syntax resolves against the Environment (properties, YAML, env vars, command line). A colon provides a default (${key:fallback}). @Value also evaluates SpEL via #{...}. For binding many related properties, prefer a typed @ConfigurationProperties class over scattering @Value everywhere.

Rule of thumb: Use @Value("${key:default}") for a handful of individual settings; switch to @ConfigurationProperties once a component reads a whole group of related keys.

@Value injects one property at a time; @ConfigurationProperties binds a whole group of related properties to a typed object. For anything beyond a couple of values, the typed approach wins.

@ConfigurationProperties(prefix = "mail")
@Component
class MailProperties {
    private String host;
    private int port = 25;
    private List<String> recipients;   // binds mail.recipients[0], [1], ...
    // getters/setters (or use a record with constructor binding)
}
mail.host=smtp.example.com
mail.port=587
mail.recipients[0][email protected]

@ConfigurationProperties gives you type safety, relaxed binding (MAIL_HOST env → mail.host), JSR-303 validation (@Validated), support for nested objects and collections, and IDE metadata. @Value can't bind collections/nested structures cleanly and scatters config across the codebase.

Rule of thumb: A single setting → @Value; a cohesive group of settings → a typed @ConfigurationProperties class with validation. The typed approach scales far better.

@ConditionalOnProperty registers a bean only when a configuration property has a given value — a clean on/off switch for features without code branches.

@Configuration
class FeatureConfig {
    @Bean
    @ConditionalOnProperty(name = "features.cache.enabled", havingValue = "true")
    CacheManager cacheManager() {        // created only if the flag is "true"
        return new CaffeineCacheManager();
    }
}
features.cache.enabled=true

You can require a specific havingValue, or use matchIfMissing = true to default to "on" when the property is absent. This is heavily used in auto-configuration and is the idiomatic way to ship optional features that users enable via configuration. For arbitrary conditions beyond properties, drop down to a custom @Conditional(Condition.class).

Rule of thumb: Gate optional beans with @ConditionalOnProperty(... havingValue=...) so a single config flag turns a feature on or off — no code change, no redeploy logic.

When the built-in conditions aren't enough, implement the Condition interface and reference it from @Conditional. Its matches method returns whether the bean should be registered.

class OnLinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata md) {
        return System.getProperty("os.name").toLowerCase().contains("linux");
    }
}

@Configuration
class PlatformConfig {
    @Bean
    @Conditional(OnLinuxCondition.class)     // bean exists only on Linux
    NativeService nativeService() { return new NativeService(); }
}

ConditionContext exposes the Environment, bean registry, classloader, and resource loader, so your condition can inspect properties, classpath, existing beans, or anything else. All of Spring Boot's @ConditionalOnX annotations are just pre-built Condition implementations on top of this SPI. Keep conditions cheap — they run at startup for every annotated definition.

Rule of thumb: For logic the built-in @ConditionalOnX annotations can't express, implement Condition and wire it via @Conditional — it's the same mechanism Spring Boot uses internally.

Apply @Scope to a @Component class or a @Bean method to override the default singleton scope — prototype, request, session, or a custom scope.

@Component
@Scope("prototype")                     // new instance per request
class Task { }

@Configuration
class Config {
    @Bean
    @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
    RequestContext requestContext() { return new RequestContext(); }
}

Built-in scopes: singleton (default), prototype, and the web scopes request, session, application, websocket. When injecting a shorter-lived scope into a singleton, set proxyMode so each access resolves the correct instance. There are convenience meta- annotations too — @RequestScope, @SessionScope, @ApplicationScope — which bundle the proxy mode for you.

Rule of thumb: Override scope with @Scope on the class or @Bean method; for web scopes prefer the @RequestScope/@SessionScope shortcuts, which set the proxy mode automatically.

@Import pulls additional @Configuration classes (or @Components, or an ImportSelector/ImportBeanDefinitionRegistrar) into the current context — a way to compose configuration from modular pieces.

@Configuration class SecurityConfig { /* security beans */ }
@Configuration class WebConfig      { /* web beans */ }

@Configuration
@Import({ SecurityConfig.class, WebConfig.class })   // compose modular config
class AppConfig { }

@Import lets you split configuration into focused classes and assemble them explicitly, rather than relying solely on component scanning. It's also the foundation of custom @Enable* annotations (like @EnableScheduling): those meta-annotate @Import to register a feature's beans. For dynamic, condition-driven imports you supply an ImportSelector.

Rule of thumb: Use @Import to compose explicit, modular @Configuration classes and to build your own @Enable* feature annotations; it's how Spring assembles config beyond plain scanning.

By default in Spring Boot, defining two beans with the same name is an error — bean definition overriding is disabled, and the context fails to start to prevent accidental shadowing.

@Configuration
class ConfigA { @Bean DataSource dataSource() { return ...; } }

@Configuration
class ConfigB { @Bean DataSource dataSource() { return ...; } }
// ✗ Two beans named "dataSource" → BeanDefinitionOverrideException at startup
# To allow later definitions to override earlier ones (rarely advisable):
spring.main.allow-bean-definition-overriding=true

The strict default catches a real class of bugs where one configuration silently replaced another's bean. The proper fixes are to rename one bean, use @Primary/@Qualifier if you actually want two of a type, or @ConditionalOnMissingBean so one only registers when the other is absent — not to flip the override flag.

Rule of thumb: Same-name beans fail by design — rename, qualify, or use @ConditionalOnMissingBean rather than enabling allow-bean-definition-overriding.

Spring Expression Language (SpEL) is evaluated inside #{...} and lets you compute values, reference other beans, and read the environment right in annotations.

@Component
class PricingService {
    @Value("#{2 * 1024 * 1024}")               // arithmetic → 2097152
    private int bufferBytes;

    @Value("#{systemProperties['user.region'] ?: 'us'}")  // env + elvis default
    private String region;

    @Value("#{discountConfig.rate}")           // read a property off another bean
    private double rate;
}

#{...} is SpEL (evaluated as an expression); ${...} is a property placeholder (looked up in the Environment) — they're different and frequently confused. You can even nest them: #{'${app.mode}' == 'fast'}. SpEL supports method calls, collection projection, ternary/elvis operators, and bean references. Keep expressions simple; complex logic belongs in code, not annotations.

Rule of thumb: #{...} = compute a SpEL expression; ${...} = inject a property. Use SpEL for small computed defaults and bean references, not for business logic.

Component scanning (@Service, @Repository, …) is concise and automatic but implicit; explicit @Bean config is verbose but gives you full control and works for classes you can't annotate. Most apps use both.

// Scanning — great for your own classes, zero boilerplate:
@Service class OrderService { }

// @Bean config — necessary for third-party types or custom construction:
@Configuration
class Config {
    @Bean ObjectMapper objectMapper() {
        return new ObjectMapper().findAndRegisterModules();
    }
}

Scanning shines for the bulk of application classes — annotate and forget. Explicit @Bean methods are required when: the class is third-party (no annotation possible), construction needs logic/builders, or you want the wiring centralized and visible in one place. The common pattern: scan your own components, declare infrastructure/library beans explicitly in a few @Configuration classes.

Rule of thumb: Scan your own code for brevity; write explicit @Bean methods for third-party types and anything needing construction logic. Real apps blend the two.

Add @Validated to a @ConfigurationProperties class and standard Jakarta Bean Validation annotations to its fields — invalid configuration then fails the app at startup instead of causing obscure runtime errors.

@ConfigurationProperties(prefix = "mail")
@Validated
@Component
class MailProperties {
    @NotBlank                    // must be present and non-empty
    private String host;

    @Min(1) @Max(65535)          // valid port range
    private int port;

    @Email
    private String from;
    // getters/setters
}

With spring-boot-starter-validation on the classpath, Spring runs the validator when it binds the properties. A bad value produces a clear BindValidationException at boot naming the offending property and constraint — much better than discovering a malformed config in production. This "fail fast on misconfiguration" is a key reason to prefer typed properties over scattered @Values.

Rule of thumb: Put @Validated + Bean Validation constraints on @ConfigurationProperties so bad configuration is caught loudly at startup, not silently at runtime.

A FactoryBean<T> is a bean that produces another object. When something injects the bean's type, Spring transparently returns the product of getObject(), not the factory itself — useful for complex or framework-driven construction.

@Component
class ClientFactoryBean implements FactoryBean<ApiClient> {
    @Override public ApiClient getObject() {
        return ApiClient.builder().retry(3).timeout(5000).build(); // complex build
    }
    @Override public Class<?> getObjectType() { return ApiClient.class; }
    @Override public boolean isSingleton() { return true; }
}

@Service
class Consumer {
    Consumer(ApiClient client) { }   // gets the ApiClient, NOT the FactoryBean
}

FactoryBean predates @Bean methods and is mostly used inside frameworks (Spring Data repositories, MyBatis mappers, and JNDI lookups are FactoryBeans under the hood). In application code a plain @Bean method is simpler and usually preferred. To get the factory itself rather than its product, prefix the name with & (ctx.getBean("&clientFactoryBean")).

Rule of thumb: In app code, prefer a @Bean method; understand FactoryBean because the frameworks you use (Spring Data, MyBatis) rely on it to manufacture proxies and clients.

Spring derives a scanned bean's name from its short class name by default. You can override that strategy globally with a custom BeanNameGenerator passed to @ComponentScan.

class FqnBeanNameGenerator implements BeanNameGenerator {
    @Override
    public String generateBeanName(BeanDefinition def, BeanDefinitionRegistry reg) {
        return def.getBeanClassName();   // use the fully-qualified class name
    }
}

@Configuration
@ComponentScan(basePackages = "com.example",
               nameGenerator = FqnBeanNameGenerator.class)
class Config { }

Customizing names is rarely needed, but it solves real problems — e.g. two classes with the same simple name in different packages, which would otherwise collide and fail. Spring Boot itself uses a FullyQualifiedAnnotationBeanNameGenerator in some setups for exactly this reason. For one-off naming you'd just pass a value to the stereotype (@Service("name")) rather than swapping the generator.

Rule of thumb: Set an explicit name on the annotation for one bean; only swap in a custom BeanNameGenerator when you have systematic naming needs like same-simple-name classes across packages.

More ways to practice

The self-quiz is live. Join our channel for updates, new content & tech tips.

Join our WhatsApp Channel