Sprint Boot Explained
Auto-Configuration
Mechanism: Spring Boot scans META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (Boot 2.7+; legacy spring.factories in older versions) for @AutoConfiguration classes on the classpath. Each is conditionally applied based on classpath presence, existing beans, and properties.
@AutoConfiguration
@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
@Bean
public DataSource dataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}
}
Key conditional annotations:
| Annotation | Triggers when |
|---|---|
@ConditionalOnClass |
A specific class is present on the classpath |
@ConditionalOnMissingBean |
No bean of that type already exists in context (lets you override defaults) |
@ConditionalOnProperty |
A property matches a given value (e.g., spring.datasource.enabled=true) |
@ConditionalOnWebApplication |
App is a web app (servlet or reactive) |
@ConditionalOnBean |
Another bean already exists |
Architect-level point: auto-configuration classes are ordered with @AutoConfigureAfter/@AutoConfigureBefore to resolve dependency ordering (e.g., DataSourceAutoConfiguration must run before JpaAutoConfiguration). To debug “why is my bean not being created,” run the app with --debug or inspect the ConditionEvaluationReport — it lists every auto-config class and the exact condition that matched or didn’t match. This is the single most useful diagnostic tool for auto-config issues and a strong interview answer to “how would you troubleshoot a missing auto-configured bean.”
You can exclude unwanted auto-configuration explicitly:
@SpringBootApplication(exclude = { SecurityAutoConfiguration.class })
Starter Dependencies
Starters are curated dependency descriptors (spring-boot-starter-*) — POMs with no code, just a tested, version-aligned set of transitive dependencies, eliminating manual version matrix management.
| Starter | Pulls in |
|---|---|
spring-boot-starter-web |
Spring MVC, embedded Tomcat, Jackson |
spring-boot-starter-data-jpa |
Hibernate, Spring Data JPA, HikariCP |
spring-boot-starter-security |
Spring Security core + config |
spring-boot-starter-test |
JUnit 5, Mockito, AssertJ, Spring Test |
spring-boot-starter-actuator |
Production-ready metrics/health endpoints |
Architect-level nuance: version alignment is governed by spring-boot-dependencies BOM (<dependencyManagement> import), not the starter itself. This is why you generally omit <version> tags for Spring-managed dependencies — overriding a transitive version without understanding the BOM’s tested combination is a common source of runtime classpath conflicts (NoSuchMethodError at runtime from version skew). When asked “how do you manage a custom internal library across microservices consistently,” the answer architects should reach for is creating your own internal BOM modeled on this same pattern.
Application Properties & YAML
Two equivalent formats; YAML preferred for hierarchical config readability, .properties for simplicity/grep-ability.
spring:
datasource:
url: jdbc:postgresql://localhost:5432/orders
hikari:
maximum-pool-size: 20
server:
port: 8081
servlet:
context-path: /api
app:
feature:
new-checkout-enabled: true
Equivalent .properties:
spring.datasource.url=jdbc:postgresql://localhost:5432/orders
spring.datasource.hikari.maximum-pool-size=20
server.port=8081
Binding to type-safe config (preferred over @Value for groups of related properties):
@ConfigurationProperties(prefix = "app.feature")
public record FeatureProperties(boolean newCheckoutEnabled) {}
Requires @EnableConfigurationProperties(FeatureProperties.class) or @ConfigurationPropertiesScan.
Property source precedence (highest wins) — frequently asked in architect interviews:
- Command-line args
SPRING_APPLICATION_JSONenv var- JNDI / Servlet init params
System.getProperties()- OS environment variables
- Profile-specific
application-{profile}.ymloutside the jar - Profile-specific
application-{profile}.ymlinside the jar application.ymloutside the jarapplication.ymlinside the jar (packaged default)@PropertySourceannotations- Default properties (
SpringApplication.setDefaultProperties)
This ordering is the basis for the standard externalized-config strategy: bake safe defaults into the jar, override via env vars or mounted config in containerized/K8s deployments (12-factor app principle) rather than rebuilding jars per environment.
Profiles (dev/test/prod)
Profiles activate environment-specific bean definitions and property overrides without code branching.
@Configuration
@Profile("dev")
public class DevConfig {
@Bean
public DataSource dataSource() { return new EmbeddedH2DataSource(); }
}
@Configuration
@Profile("prod")
public class ProdConfig {
@Bean
public DataSource dataSource() { return buildPooledPostgresDataSource(); }
}
# application.yml — common defaults
spring:
profiles:
active: dev
---
spring:
config:
activate:
on-profile: prod
server:
port: 80
logging:
level:
root: WARN
Activation methods (in addition to property precedence above): -Dspring.profiles.active=prod, SPRING_PROFILES_ACTIVE=prod env var, or programmatically via SpringApplicationBuilder.
Architect-level nuance — multiple profiles & groups: profiles can be combined (prod,eu-region), and Boot 2.4+ supports spring.profiles.group to bundle several profiles under one activation flag (e.g., activating production auto-activates prod,monitoring,audit-logging). This is the cleaner pattern over giant conditional profile chains for multi-region/multi-tenant deployments — a good answer when asked how you’d structure config for a system deployed across multiple regions/environments (relevant to your Smith & Nephew multi-region delivery context).
Pitfall to flag in reviews: don’t gate security-relevant beans behind @Profile("dev") defaults that silently weaken posture (e.g., disabling CSRF) — explicitly verify the active profile at deploy time via Actuator’s /actuator/env.
Embedded Servers (Tomcat/Jetty/Netty)
Spring Boot bundles the servlet container inside the executable jar — no external app server deployment (no WAR-to-Tomcat-instance ceremony).
| Server | Type | Default for | Use case |
|---|---|---|---|
| Tomcat | Servlet (blocking, thread-per-request) | spring-boot-starter-web (default) |
General-purpose synchronous REST APIs |
| Jetty | Servlet (blocking, lighter footprint) | Swap via excluding Tomcat, adding starter-jetty |
Lower memory footprint, embedded/IoT-adjacent use |
| Netty | Reactive (non-blocking, event-loop) | spring-boot-starter-webflux (default) |
High-concurrency, I/O-bound, reactive/WebFlux stacks |
| Undertow | Servlet or reactive, very lightweight | Swap-in alternative | High-throughput, low-overhead |
Swapping the default:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
Architect decision criteria (common system-design interview thread):
- Tomcat/Jetty (servlet, blocking): simpler mental model, mature ecosystem, fine when downstream calls are fast or thread pool sizing covers peak concurrency. Thread-per-request model caps concurrency at thread pool size — a slow downstream dependency exhausts threads under load.
- Netty (WebFlux, reactive): scales to very high concurrent connections per node with bounded threads (event loop), but requires fully non-blocking I/O end-to-end — mixing in a blocking JDBC call defeats the purpose and blocks the event loop. Steeper learning curve (
Mono/Flux, backpressure) and harder debugging (stack traces span async boundaries).
Configuration is unified via server.* properties regardless of which container is active:
server:
port: 8080
tomcat:
threads:
max: 200
connection-timeout: 5s
Spring Boot CLI
A command-line tool for rapidly prototyping Spring applications from Groovy scripts — runs Groovy code directly with automatic dependency resolution, without a full Maven/Gradle project structure.
# Install via SDKMAN
sdk install springboot
# Run a single-file Groovy app directly
spring run app.groovy
// app.groovy
@RestController
class HelloController {
@RequestMapping("/")
String home() { "Hello from Spring Boot CLI" }
}
CLI auto-detects @Grab-style dependencies and Spring annotations to resolve the classpath without an explicit pom.xml.
Honest architect-level take: Spring Boot CLI is now largely a legacy/niche tool — it’s rarely used in production engineering workflows today. Most teams reach for Spring Initializr (start.spring.io, or its IDE/CLI integrations) to bootstrap a proper Maven/Gradle project structure instead. If this comes up in an interview, it’s worth knowing it exists and what problem it solved (zero-ceremony scripting/prototyping), but flagging that it’s not part of a modern day-to-day toolchain shows current, practical awareness rather than dated knowledge — a good way to differentiate yourself from candidates reciting outdated material.