← Spring Boot & Microservices Interview Guide
Chapter 18 min read

Spring Boot Fundamentals & Internals Interview Questions

How Spring Boot auto-configuration actually works internally, dependency injection trade-offs, and the questions that separate a candidate who's used annotations from one who understands the container.

Q1. How does Spring Boot auto-configuration actually work internally?

Auto-configuration automatically wires beans based on what's on the classpath, but the mechanism behind that is worth being able to explain past the one-sentence version.

1. Entry point — @SpringBootApplication

@SpringBootApplication
// equivalent to:
@SpringBootConfiguration  // = @Configuration
@EnableAutoConfiguration  // triggers auto-config
@ComponentScan            // scans for components
public class Application {}

2. @EnableAutoConfiguration mechanism

  • SpringFactoriesLoader reads META-INF/spring.factories (or, on newer Boot versions, META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports) from every JAR on the classpath
  • Loads every class listed under org.springframework.boot.autoconfigure.EnableAutoConfiguration
  • Each auto-configuration class is then evaluated

3. Conditional annotations decide whether each one actually applies

  • @ConditionalOnClass — only if specific classes exist on the classpath
  • @ConditionalOnMissingBean — only if a bean of that type doesn't already exist
  • @ConditionalOnProperty — based on a configuration property
  • @ConditionalOnWebApplication — only for web applications

4. A real auto-configuration class looks like this

@Configuration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@ConditionalOnMissingBean(DataSource.class)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource(DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }
}

The key point interviewers are listening for: your own @Bean definitions always win, because the auto-configured ones are guarded by @ConditionalOnMissingBean. Spring Boot never silently overrides something you explicitly configured.

To see this happening live, set debug=true in application.properties — it prints every auto-configuration class Spring evaluated and whether it matched or was excluded, which is the fastest way to actually debug "why isn't my bean being created."

Q2. Compare constructor, setter, and field injection — which do you actually use, and why?

All three wire the same dependency, but they're not equivalent in practice.

// Constructor injection — preferred
@Service
public class OrderService {
    private final PaymentClient paymentClient;

    public OrderService(PaymentClient paymentClient) {
        this.paymentClient = paymentClient;
    }
}

// Field injection — avoid in production code
@Service
public class OrderService {
    @Autowired
    private PaymentClient paymentClient;
}

Constructor injection wins for three concrete reasons, not just convention:

  1. Immutability — the field can be final, so it's impossible to reassign it after construction, and the class can't exist in a half-initialized state.
  2. Fails fast at startup, not at runtime — if a required dependency is missing, the application context fails to start with a clear error, instead of throwing a NullPointerException the first time the field is actually used.
  3. Testability without a Spring context — you can construct the object directly with new OrderService(mockPaymentClient) in a unit test. Field injection needs reflection or a full Spring context to populate the dependency at all.

Field injection's only real argument in its favor is less boilerplate, which modern IDEs and Lombok's @RequiredArgsConstructor eliminate anyway — so there's rarely a good reason to reach for it in anything beyond a quick prototype.

Q3. What are Spring Profiles and how do you use them effectively in microservices?

Spring Profiles allow you to segregate application configuration and make it available only in certain environments.

1. Defining Profiles

// application.properties (common config)
app.name=MyService

// application-dev.properties
spring.datasource.url=jdbc:h2:mem:testdb
logging.level.root=DEBUG

// application-prod.properties
spring.datasource.url=jdbc:mysql://prod-db:3306/mydb
logging.level.root=WARN

2. Activating Profiles

// Method 1: application.properties
spring.profiles.active=dev

// Method 2: Command line
java -jar app.jar --spring.profiles.active=prod

// Method 3: Environment variable
export SPRING_PROFILES_ACTIVE=prod

// Method 4: Programmatically
SpringApplication app = new SpringApplication(Application.class);
app.setAdditionalProfiles("dev");
app.run(args);

3. Profile-Specific Beans

@Configuration
@Profile("dev")
public class DevConfig {
    
    @Bean
    public DataSource dataSource() {
        return new H2DataSource(); // In-memory for dev
    }
}

@Configuration
@Profile("prod")
public class ProdConfig {
    
    @Bean
    public DataSource dataSource() {
        return new MySQLDataSource(); // Production DB
    }
}

4. Multiple Profiles

// Activate multiple profiles
spring.profiles.active=dev,debug,local

// Profile expressions
@Profile("!prod")  // NOT prod
@Profile("dev | test")  // dev OR test
@Profile("cloud & mysql")  // cloud AND mysql

5. YAML Configuration (Better for profiles)

spring:
  profiles:
    active: dev
---
spring:
  config:
    activate:
      on-profile: dev
  datasource:
    url: jdbc:h2:mem:testdb
---
spring:
  config:
    activate:
      on-profile: prod
  datasource:
    url: jdbc:mysql://prod-db:3306/mydb

6. Microservices Best Practices

  • local: Developer machine (H2, mocks)
  • dev: Development environment
  • qa: QA/Testing environment
  • staging: Pre-production
  • prod: Production

7. Profile Groups (Spring Boot 2.4+)

spring:
  profiles:
    group:
      production:
        - "prod"
        - "metrics"
        - "monitoring"
      local:
        - "dev"
        - "debug"
        - "h2"

# Activate: spring.profiles.active=production
# This activates: prod, metrics, monitoring

8. Checking Active Profile in Code

@Component
public class ProfileChecker {
    
    @Autowired
    private Environment environment;
    
    public void checkProfile() {
        String[] activeProfiles = environment.getActiveProfiles();
        boolean isProd = environment.acceptsProfiles(Profiles.of("prod"));
        
        if (isProd) {
            // Production-specific logic
        }
    }
}

Profiles are useful for selecting environment-specific wiring, but they should not become a substitute for externalized secrets or a long list of hidden production switches. The senior answer is to keep profile differences small, explicit, and safe to verify in deployment.

Q4. How does Spring resolve which bean to inject when multiple implementations exist?

The constructor-vs-setter-vs-field decision above settles how a dependency gets wired in. This question is different: once there are two or more beans of the same type on the classpath, Spring needs a way to know which one you mean, or it fails to start with a NoUniqueBeanDefinitionException.

1. @Qualifier for Multiple Beans

// Define multiple implementations
@Component("stripeProcessor")
public class StripeProcessor implements PaymentProcessor {}

@Component("paypalProcessor")
public class PaypalProcessor implements PaymentProcessor {}

// Use @Qualifier
@Service
public class PaymentService {
    private final PaymentProcessor processor;
    
    public PaymentService(@Qualifier("stripeProcessor") 
                         PaymentProcessor processor) {
        this.processor = processor;
    }
}

2. @Primary Annotation

@Component
@Primary  // Default choice when no @Qualifier specified
public class DefaultPaymentProcessor implements PaymentProcessor {}

3. Custom Qualifiers

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface PaymentType {
    String value();
}

@Component
@PaymentType("stripe")
public class StripeProcessor implements PaymentProcessor {}

// Usage:
public PaymentService(@PaymentType("stripe") 
                     PaymentProcessor processor) {}

@Qualifier is explicit at every injection site, @Primary sets a quiet default that's easy to forget about six months later, and a custom qualifier annotation is worth the extra ceremony once you have more than two or three implementations of the same interface and @Qualifier("stripeProcessor") string literals start drifting out of sync with the actual bean names.

Q5. Explain @Configuration vs @Component. When to use which?

@Component

@Component
public class UserService {
    // Spring creates ONE instance
    // Managed as singleton bean
}

Use for: Regular Spring-managed beans, services, repositories

@Configuration

@Configuration
public class AppConfig {
    
    @Bean
    public DataSource dataSource() {
        return new HikariDataSource();
    }
    
    @Bean
    public UserService userService() {
        return new UserService(dataSource()); // Proxied!
    }
}

Key Difference: CGLIB Proxying

@Configuration  // Creates CGLIB proxy
public class Config {
    @Bean
    public BeanA beanA() {
        return new BeanA();
    }
    
    @Bean
    public BeanB beanB() {
        return new BeanB(beanA()); // Returns SAME instance from container
    }
}

@Component  // NO proxy
public class NotAConfig {
    @Bean
    public BeanA beanA() {
        return new BeanA();
    }
    
    @Bean
    public BeanB beanB() {
        return new BeanB(beanA()); // Creates NEW instance
    }
}

When to Use What:

ScenarioUse
Define multiple @Bean methods@Configuration
Beans depend on each other@Configuration
Regular business logic@Component
Third-party library beans@Configuration + @Bean

The distinction that matters in production is proxy behavior: use @Configuration when @Bean methods depend on one another and must return the container-managed singleton, and use @Component for ordinary component scanning.

Q6. How do you optimize Spring Boot application startup time?

1. Lazy Initialization

spring.main.lazy-initialization=true

Impact: 40-50% faster startup, but first request slower

2. Component Scan Optimization

@SpringBootApplication(scanBasePackages = "com.company.specific")
// Instead of scanning entire classpath

3. Exclude Unused Auto-Configurations

@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class,
    HibernateJpaAutoConfiguration.class
})

4. Profile-Specific Loading

spring.profiles.active=prod
# Don't load dev/test configs in production

5. JVM Optimizations

java -jar app.jar \
  -XX:TieredStopAtLevel=1 \  # Fast startup (dev)
  -XX:+UseG1GC \              # Better GC
  -Xms512m -Xmx512m           # Fixed heap

6. Spring Boot 3+ Native Image

./mvnw -Pnative native:compile
# Result: <100ms startup

7. Database Connection Pool Tuning

spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=20

Typical app startup can drop from 10–15 seconds to 3–5 seconds, lazy initialization can reduce that further to roughly 2–3 seconds, and a native image can start in under 100 milliseconds. The trade-off is that lazy initialization shifts some work to the first request, while native images add build complexity and constraints that only pay off when startup and memory are operational priorities.

The production detail worth mentioning is that startup optimization should be measured against readiness time and first-request latency, not treated as a race to the smallest number on a local machine.

Q7. Explain @SpringBootApplication annotation in detail.

@SpringBootApplication
// This is a convenience annotation equivalent to:
@SpringBootConfiguration  // = @Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {}

Breaking it down:

  • @SpringBootConfiguration: Marks class as configuration source
  • @EnableAutoConfiguration: Triggers auto-configuration
  • @ComponentScan: Scans current package + sub-packages

Customization:

@SpringBootApplication(
    scanBasePackages = {"com.app.core", "com.app.api"},
    exclude = {DataSourceAutoConfiguration.class}
)

The interview point is that the annotation is a convenience composition, while its component scan and auto-configuration boundaries still determine what the application can see and which defaults Spring Boot applies.

Q8. What are Spring Boot Starters? How do they work?

Starters are dependency descriptors that bring in all required dependencies.

Common Starters:

spring-boot-starter-web        // Web + Tomcat + JSON
spring-boot-starter-data-jpa   // JPA + Hibernate
spring-boot-starter-security   // Spring Security
spring-boot-starter-actuator   // Monitoring
spring-boot-starter-test       // Testing

What happens when you add starter-web:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

// Brings in:
// - spring-web
// - spring-webmvc  
// - tomcat-embed-core
// - jackson-databind
// - validation-api
// + 20 more dependencies!

Name convention: xxx-spring-boot-starter

The useful distinction is that a starter manages a coherent dependency set; it does not itself create application behavior until the matching auto-configuration sees those dependencies on the classpath.

Krishna Kumar Yadav

Krishna Kumar Yadav

Senior Software Engineer building distributed systems at scale. 9+ years across fintech, airline tech, and startups.