> ## Content Index
> Fetch the complete content index at: https://www.codyssey.tech/llms.txt
> Use this file to discover other available public pages before exploring further.

# ⚙️ Spring Boot Alchemy: Turning Configuration into a Running Service
- URL: https://www.codyssey.tech/spring-boot-alchemy/
- Published: 2026-04-22T16:00:01.000Z
- Updated: 2026-05-14T07:36:51.000Z
- Description: Spring Boot auto-configuration feels like magic until something breaks at 2 AM. This article deconstructs the PROPS framework — profiles, property binding, bean lifecycle, and starter dependencies — so you understand every piece of the machinery running your microservice.
- Author: Robert Marcel Saveanu
- Tags: Architecture & Infra, Microservices, Spring Boot, #format-tutorial, #series-spring-boot, #syntax-highlight

📚 **Series Navigation:**  
← **Previous:** [Part 1 - The Blueprint Before the Build](https://www.codyssey.tech/the-blueprint-before-the-build/)  
👉 **You are here:** Part 2 - Spring Boot Alchemy  
**Next:** [Part 3 - REST Assured](https://www.codyssey.tech/rest-assured/) →

---

## 📋 Introduction

There's a particular kind of magic that happens when you press "Run" on a Spring Boot application. One moment you have a YAML file and some annotated classes. The next, you have a fully running web server with connection pooling, transaction management, caching, security, metrics, and more — all wired together without you writing a single line of plumbing code.

Developers new to Spring Boot often take this for granted. *Of course* my REST endpoints just work. *Of course* Hibernate is configured. *Obviously* my cache is working. But under the hood, there's an intricate dance of auto-configuration, condition evaluation, property binding, and bean lifecycle management that makes it all possible.

Understanding this magic is the difference between a developer who *uses* Spring Boot and one who *masters* it. When something goes wrong — and it will — the developer who understands auto-configuration can diagnose the issue in minutes. The one who doesn't will spend hours on Stack Overflow wondering why their beans aren't being created.

In this article, we'll peel back the curtain on the Weather Microservice's configuration. We'll see how a 243-line YAML file, a handful of configuration classes, and a carefully curated `pom.xml` come together to create a production-ready application. Let's get started. ☕

---

## ⚙️ The PROPS Framework: Five Pillars of Spring Boot Configuration

Let me introduce the **PROPS** framework — five principles that guide how we think about configuring Spring Boot applications:

| Letter | Principle                | What It Means                                              |
| ------ | ------------------------ | ---------------------------------------------------------- |
| **P**  | **Profile-driven**       | Different environments get different configurations        |
| **R**  | **Runtime Binding**      | Properties bind to typed Java objects at startup           |
| **O**  | **Overridable Defaults** | Sensible defaults that can be overridden via env vars      |
| **P**  | **Property Precedence**  | Clear hierarchy from defaults → YAML → env vars → CLI args |
| **S**  | **Starter Dependencies** | One dependency brings in everything you need for a feature |

Let's explore each one through the lens of the Weather Microservice.

---

## 🚀 The Entry Point: Where It All Begins

Every Spring Boot journey starts with a single class:

```java
// WeatherApplication.java
@SpringBootApplication
@EnableCaching
public class WeatherApplication {

  public static void main(String[] args) {
    SpringApplication app = new SpringApplication(WeatherApplication.class);

    Runtime.getRuntime()
        .addShutdownHook(
            new Thread(
                () -> System.out.println("\nShutting down WeatherSpring gracefully...")));

    app.run(args);
  }
}

```

That `@SpringBootApplication` annotation is doing more work than your most productive team member. It's actually three annotations in one:

| Annotation               | What It Does                                           |
| ------------------------ | ------------------------------------------------------ |
| @SpringBootConfiguration | Marks this as a Spring configuration class             |
| @EnableAutoConfiguration | Triggers Spring Boot's auto-configuration magic        |
| @ComponentScan           | Scans com.weatherspring and all sub-packages for beans |

The `@EnableCaching` annotation activates Spring's cache abstraction. Without it, all those `@Cacheable` annotations on our services would be decorative.

Notice the shutdown hook — it provides a clean console message when the application stops. Small touch, big impact when you're watching logs during a deployment.

---

## 📄 The Configuration File: 243 Lines of Power

The `application.yml` file is the nerve center of the Weather Microservice. Let's walk through it section by section.

### Application Identity & Virtual Threads

```yaml
spring:
  application:
    name: weather-service

  threads:
    virtual:
      enabled: true

  profiles:
    active: dev

```

Three lines. That's all it takes to:

1. **Name the application** — Used in metrics tags, logging, and service discovery
2. **Enable virtual threads** — Handles 10,000+ concurrent requests vs \~200 with platform threads
3. **Set the default profile** — `dev` for local development

> 🔥 **Critical Insight:** `spring.threads.virtual.enabled: true` is arguably the single most impactful line in this entire YAML file. It switches Spring Boot's embedded Tomcat to use virtual threads (Project Loom), fundamentally changing the concurrency model. We'll deep-dive into this in Part 9.

### MVC & Problem Details

```yaml
  mvc:
    throw-exception-if-no-handler-found: true
    problemdetails:
      enabled: true

```

Two settings worth understanding:

- **`throw-exception-if-no-handler-found: true`** — Ensures that requests to unmapped URLs throw a `NoHandlerFoundException` instead of silently returning a blank 404.  
This lets our `GlobalExceptionHandler` catch it and return a proper RFC 7807 response.
- **`problemdetails.enabled: true`** — Activates Spring's built-in RFC 7807 ProblemDetail support, which standardizes error responses across the entire application.

### JPA & Hibernate

```yaml
  jpa:
    show-sql: false
    hibernate:
      ddl-auto: validate
    properties:
      hibernate:
        format_sql: true
        dialect: org.hibernate.dialect.H2Dialect

```

The critical setting here is `ddl-auto: validate`. This tells Hibernate to **check** that the database schema matches the entity mappings, but **never** modify the schema. Schema changes go through Flyway migrations — always.

| ddl-auto Value | What It Does                         | Safe for Production? |
| -------------- | ------------------------------------ | -------------------- |
| none           | Does nothing                         | ✅ Yes                |
| validate       | Checks schema matches entities       | ✅ Yes                |
| update         | Modifies schema to match entities    | ❌ Never              |
| create         | Drops and recreates on every startup | ❌ Never              |
| create-drop    | Creates on start, drops on stop      | ❌ Never              |

> 💡 **Pro Tip:** Always use `validate` in development and `validate` or `none` in production. Using `update` in production is how you get mysterious column additions and data loss.

### Flyway Database Migrations

```yaml
  flyway:
    enabled: true
    baseline-on-migrate: true
    baseline-version: 0
    locations: classpath:db/migration
    validate-on-migrate: true

```

Flyway manages all database schema changes through versioned migration scripts. `baseline-on-migrate: true` lets Flyway work with existing databases by creating a baseline version. `validate-on-migrate: true` verifies that migration checksums haven't been tampered with — protecting against accidental modifications to applied migrations.

### Datasource with Environment Variable Overrides

```yaml
  datasource:
    url: ${DATABASE_URL:jdbc:h2:file:./data/weatherdb;AUTO_SERVER=TRUE}
    driver-class-name: org.h2.Driver
    username: ${DATABASE_USERNAME:sa}
    password: ${DATABASE_PASSWORD:}

```

This is the **Overridable Defaults** principle in action. The syntax `${DATABASE_URL:jdbc:h2:file:./data/weatherdb}` means:

- Use the `DATABASE_URL` environment variable if it exists
- Fall back to `jdbc:h2:file:./data/weatherdb` if it doesn't

For local development, you get an H2 file-based database with zero setup. In production, you set `DATABASE_URL` to your PostgreSQL connection string. Same code, different environments, no code changes.

### Server Configuration

```yaml
server:
  port: 8080
  shutdown: graceful
  compression:
    enabled: true
    mime-types: application/json,application/xml,text/html,text/xml,text/plain
    min-response-size: 1024

```

Three important settings:

- **`shutdown: graceful`** — When the app receives SIGTERM, it stops accepting new requests but finishes processing in-flight requests.  
Combined with `spring.lifecycle.timeout-per-shutdown-phase: 30s`, this gives requests up to 30 seconds to complete.
- **Response compression** — JSON responses larger than 1KB are automatically gzip-compressed, reducing bandwidth by 60-80% for typical API responses.

### Management & Actuator

```yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus,circuitbreakers,circuitbreakerevents,shutdown
    enabled-by-default: false
  endpoint:
    health:
      enabled: true
      show-details: when-authorized
      roles: ACTUATOR_ADMIN
    prometheus:
      enabled: true
    shutdown:
      enabled: true

```

The actuator configuration follows the **principle of least privilege**:

1. **All endpoints disabled by default** (`enabled-by-default: false`)
2. **Only specific endpoints are enabled** — health, metrics, prometheus, etc.
3. **Health details require authorization** — Only users with `ACTUATOR_ADMIN` role see full health details
4. **Shutdown is enabled** — But protected by security (Part 7)

This is security-conscious configuration. In production, you don't want random users hitting `/actuator/env` and seeing your environment variables.

### Resilience4j Configuration

```yaml
resilience4j:
  circuitbreaker:
    configs:
      default:
        failureRateThreshold: 50
        minimumNumberOfCalls: 5
        waitDurationInOpenState: 10s
        slidingWindowSize: 10
    instances:
      weatherApiCurrent:
        baseConfig: default
        failureRateThreshold: 50
        waitDurationInOpenState: 15s
        minimumNumberOfCalls: 10

  retry:
    configs:
      default:
        maxAttempts: 3
        waitDuration: 1s
        enableExponentialBackoff: true
        exponentialBackoffMultiplier: 2

  ratelimiter:
    instances:
      weatherApi:
        limitForPeriod: 50
        limitRefreshPeriod: 1m

```

This is a masterclass in hierarchical configuration:

1. **Default configs** define the baseline behavior for all instances
2. **Named instances** override specific settings (e.g., forecast circuit breaker has a longer wait because forecast calls are slower)
3. **`baseConfig: default`** inherits from the default template

We'll explore Resilience4j deeply in Part 5, but notice how configuration-driven this approach is. No code changes needed to tune retry counts or failure thresholds.

### Weather API & Thread Pool Configuration

```yaml
weather:
  api:
    base-url: https://api.weatherapi.com/v1
    key: ${WEATHER_API_KEY}
    timeout: 5000
    cache:
      current-weather-ttl: 300
      forecast-ttl: 3600
      location-ttl: 900

thread-pool:
  platform:
    core-pool-size: ${PLATFORM_EXECUTOR_CORE_POOL_SIZE:10}
    max-pool-size: ${PLATFORM_EXECUTOR_MAX_POOL_SIZE:50}
    queue-capacity: ${PLATFORM_EXECUTOR_QUEUE_CAPACITY:500}
    await-termination-seconds: ${PLATFORM_EXECUTOR_AWAIT_TERMINATION:30}

```

Custom configuration properties for the application's own concerns. The thread pool config uses environment variable overrides so production can tune pool sizes without code changes.

---

## 🔀 Profile Power: One Codebase, Multiple Environments

The Weather Microservice uses Spring profiles to manage environment-specific configuration. The base `application.yml` uses H2 and dev-friendly settings. The production profile (`application-prod.yml`) overrides what needs to change:

```yaml
# application-prod.yml
spring:
  jpa:
    hibernate:
      ddl-auto: validate
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
        jdbc:
          batch_size: 20
        order_inserts: true
        order_updates: true

  datasource:
    url: ${DATABASE_URL:jdbc:postgresql://localhost:5432/weatherspring}
    driver-class-name: org.postgresql.Driver
    hikari:
      maximum-pool-size: ${DB_POOL_SIZE:20}
      minimum-idle: ${DB_POOL_MIN_IDLE:5}
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      leak-detection-threshold: 60000
      pool-name: WeatherServicePool

server:
  error:
    include-stacktrace: never
    include-message: never

logging:
  level:
    root: INFO
    com.weatherspring: INFO
    org.springframework: WARN

```

Key differences from dev:

| Aspect                 | Dev (default)    | Production                       |
| ---------------------- | ---------------- | -------------------------------- |
| Database               | H2 (file-based)  | PostgreSQL                       |
| Dialect                | H2Dialect        | PostgreSQLDialect                |
| Connection Pool        | Default HikariCP | Tuned with leak detection        |
| Stack traces in errors | Shown            | never                            |
| Error messages         | Shown            | never                            |
| Logging level          | DEBUG for app    | INFO for app, WARN for framework |
| Batch inserts          | Disabled         | Enabled (batch\_size=20)         |

> 🔥 **Critical Insight:** `include-stacktrace: never` and `include-message: never` in production prevent leaking internal details to API consumers. Never expose stack traces in production — they reveal class names, line numbers, and library versions that attackers can exploit.

### Activating Profiles

```bash
# Local development (default)
./mvnw spring-boot:run

# Production
SPRING_PROFILES_ACTIVE=prod java -jar weather-service.jar

# Docker
docker run -e SPRING_PROFILES_ACTIVE=prod weather-service

# Kubernetes (via Helm values)
env:
  - name: SPRING_PROFILES_ACTIVE
    value: prod

```

---

## ⚙️ Type-Safe Configuration with @ConfigurationProperties

Rather than scattering `@Value` annotations throughout the codebase, the Weather Microservice uses type-safe configuration binding. Here's the `AsyncConfig` class:

```java
@Configuration
@EnableAsync
@ConfigurationProperties(prefix = "thread-pool.platform")
@Validated
@Getter
@Setter
public class AsyncConfig {

  @Min(1) @Max(100)
  private int corePoolSize = 10;

  @Min(1) @Max(500)
  private int maxPoolSize = 50;

  @Min(0) @Max(10000)
  private int queueCapacity = 500;

  @Min(1) @Max(300)
  private int awaitTerminationSeconds = 30;

  @PostConstruct
  public void validateConfig() {
    if (maxPoolSize < corePoolSize) {
      throw new IllegalStateException(
          String.format("max-pool-size (%d) must be >= core-pool-size (%d)",
              maxPoolSize, corePoolSize));
    }
  }
}

```

This binds directly to the YAML configuration:

```yaml
thread-pool:
  platform:
    core-pool-size: 10
    max-pool-size: 50
    queue-capacity: 500

```

The benefits over `@Value`:

| Feature            | @Value                | @ConfigurationProperties         |
| ------------------ | --------------------- | -------------------------------- |
| Type safety        | ❌ String at runtime   | ✅ Compiled types                 |
| Validation         | ❌ Manual              | ✅ Bean Validation annotations    |
| IDE support        | ❌ No autocomplete     | ✅ Full IDE support               |
| Grouped properties | ❌ Scattered           | ✅ Logical grouping               |
| Default values     | ✅ Via ${prop:default} | ✅ Via field initializers         |
| Relaxed binding    | ❌ Exact match         | ✅ core-pool-size \= corePoolSize |

The `@Validated` annotation enables Bean Validation on the config properties. If someone sets `core-pool-size: -5` in YAML, the application fails fast at startup with a clear validation error — not at runtime when the first async task runs.

The `@PostConstruct` validation adds business logic that Bean Validation can't express: `maxPoolSize` must be >= `corePoolSize`. This runs at startup, failing immediately with a descriptive error.

---

## 📦 Starter Dependencies: The Curated Menu

The Weather Microservice's `pom.xml` tells a story about what the application does. Each starter dependency brings in a curated set of libraries:

```xml
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.5.7</version>
</parent>

<properties>
    <java.version>25</java.version>
</properties>

```

The parent POM manages dependency versions centrally. You declare `spring-boot-starter-web` without a version, and the parent ensures compatibility with all other Spring Boot dependencies.

### The Starter Lineup

| Starter                        | What It Brings                               |
| ------------------------------ | -------------------------------------------- |
| spring-boot-starter-web        | Embedded Tomcat, Spring MVC, Jackson JSON    |
| spring-boot-starter-data-jpa   | Hibernate, Spring Data JPA, HikariCP         |
| spring-boot-starter-validation | Hibernate Validator, Jakarta Bean Validation |
| spring-boot-starter-cache      | Spring Cache abstraction                     |
| spring-boot-starter-actuator   | Health checks, metrics, monitoring endpoints |
| spring-boot-starter-security   | Spring Security, auth infrastructure         |
| spring-boot-starter-aop        | AspectJ support (needed for Resilience4j)    |

### Beyond Starters: Specialized Libraries

```xml
<!-- Resilience4j: Circuit breaker, retry, rate limiting -->
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
    <version>${resilience4j.version}</version>
</dependency>

<!-- Caffeine: High-performance cache -->
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>${caffeine.version}</version>
</dependency>

<!-- Distributed tracing -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>

<!-- Structured JSON logging -->
<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>${logstash-logback.version}</version>
</dependency>

```

Each dependency triggers specific auto-configuration:

- **Caffeine on classpath** → Spring auto-configures `CaffeineCacheManager`
- **Micrometer on classpath** → Spring auto-configures metrics collection
- **Resilience4j on classpath** → Spring auto-configures circuit breaker registry

This is the magic of auto-configuration: **presence on the classpath implies intent.** Add the jar, get the feature.

---

## 🏗️ Configuration Classes: The Custom Wiring

When auto-configuration isn't enough, the Weather Microservice uses explicit `@Configuration` classes.

### RestClient Configuration

```java
@Configuration
public class RestClientConfig {

  @Value("${weather.api.timeout:5000}")
  private long timeout;

  @Value("${weather.api.base-url}")
  private String baseUrl;

  @Bean
  public RestClient weatherRestClient() {
    HttpClient httpClient =
        HttpClient.newBuilder()
            .connectTimeout(Duration.ofMillis(timeout))
            .executor(Executors.newVirtualThreadPerTaskExecutor())
            .build();

    JdkClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(httpClient);
    requestFactory.setReadTimeout(Duration.ofMillis(timeout));

    return RestClient.builder()
        .baseUrl(baseUrl)
        .requestFactory(requestFactory)
        .defaultHeader("Accept", "application/json")
        .build();
  }
}

```

This creates a `RestClient` bean that:

1. Uses Java's modern `HttpClient` (not Apache HttpClient)
2. Configures **virtual threads** for the HTTP executor — HTTP calls don't block platform threads
3. Sets connect and read timeouts from configuration
4. Sets the base URL so API calls can use relative paths

### Async Configuration with Three Executors

```java
@Configuration
@EnableAsync
@ConfigurationProperties(prefix = "thread-pool.platform")
@Validated
public class AsyncConfig {

  @Bean(name = "taskExecutor")
  public Executor taskExecutor() {
    return Executors.newVirtualThreadPerTaskExecutor();
  }

  @Bean(name = "compositeExecutor", destroyMethod = "close")
  public ExecutorService compositeExecutor() {
    return Executors.newVirtualThreadPerTaskExecutor();
  }

  @Bean(name = "platformExecutor")
  public Executor platformExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(corePoolSize);
    executor.setMaxPoolSize(maxPoolSize);
    executor.setQueueCapacity(queueCapacity);
    executor.setThreadNamePrefix("platform-async-");
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setAwaitTerminationSeconds(awaitTerminationSeconds);
    executor.initialize();
    return executor;
  }
}

```

Three executors for different purposes:

| Executor          | Type             | Purpose                        |
| ----------------- | ---------------- | ------------------------------ |
| taskExecutor      | Virtual threads  | Default @Async executor        |
| compositeExecutor | Virtual threads  | Parallel weather data fetching |
| platformExecutor  | Platform threads | CPU-bound fallback tasks       |

The `compositeExecutor` uses `destroyMethod = "close"` because virtual thread executor services implement `AutoCloseable`, not the traditional `ExecutorService.shutdown()` method.

---

## 🔄 Property Precedence: Who Wins?

Spring Boot has a well-defined property precedence order. For the Weather Microservice, the practical hierarchy is:

```
1. Command-line arguments     (highest priority)
   java -jar app.jar --server.port=9090

2. Environment variables
   SERVER_PORT=9090

3. application-{profile}.yml
   application-prod.yml

4. application.yml
   application.yml

5. @ConfigurationProperties defaults
   private int corePoolSize = 10;      (lowest priority)

```

This means a Kubernetes environment variable will always override a YAML setting, which will override a default. The Weather Microservice exploits this heavily:

```yaml
# application.yml - sensible defaults
datasource:
  url: ${DATABASE_URL:jdbc:h2:file:./data/weatherdb}

# Kubernetes deployment - env var overrides
env:
  - name: DATABASE_URL
    value: jdbc:postgresql://db:5432/weather

```

No code changes. No recompilation. Just environment variables.

---

## 🔧 Build Configuration: The Maven Setup

The `pom.xml` isn't just a dependency list — it's a quality gate:

### Compiler Configuration

```xml
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <release>25</release>
        <compilerArgs>
            <arg>-parameters</arg>
        </compilerArgs>
        <annotationProcessorPaths>
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

```

The `-parameters` flag is critical — it preserves method parameter names in bytecode, which Spring uses for:

- `@PathVariable` and `@RequestParam` automatic name matching
- Constructor parameter name resolution for `@ConfigurationProperties`
- Better error messages in validation failures

### Quality Tools

```xml
<!-- JaCoCo: 80% line coverage gate -->
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <configuration>
        <rules>
            <rule>
                <element>BUNDLE</element>
                <limits>
                    <limit>
                        <counter>LINE</counter>
                        <value>COVEREDRATIO</value>
                        <minimum>0.80</minimum>
                    </limit>
                </limits>
            </rule>
        </rules>
    </configuration>
</plugin>

<!-- Spotless: Code formatting -->
<plugin>
    <groupId>com.diffplug.spotless</groupId>
    <artifactId>spotless-maven-plugin</artifactId>
</plugin>

<!-- Checkstyle: Style enforcement -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-checkstyle-plugin</artifactId>
</plugin>

```

Every build enforces:

- **80% line coverage** — JaCoCo fails the build if coverage drops below 80%
- **Consistent formatting** — Spotless auto-formats code on build
- **Style rules** — Checkstyle enforces Google Java Style

The JaCoCo configuration also wisely excludes classes that don't need testing:

```xml
<excludes>
    <exclude>**/*Dto.class</exclude>        <!-- Records - just data -->
    <exclude>**/model/**/*.class</exclude>  <!-- JPA entities - Lombok -->
    <exclude>**/config/**/*.class</exclude> <!-- Config - tested via integration -->
</excludes>

```

---

## 🎯 Auto-Configuration in Action

Here's what happens when the Weather Microservice starts:

1. **Component scan** discovers all `@Component`, `@Service`, `@Controller`, `@Repository`, `@Configuration` classes
2. **Auto-configuration** evaluates 100+ `@Conditional` conditions:  
  - H2 on classpath + datasource config → `DataSourceAutoConfiguration`
  - Hibernate + JPA properties → `HibernateJpaAutoConfiguration`
  - Caffeine on classpath + `spring.cache.type=caffeine` → `CaffeineCacheConfiguration`
  - Spring Security on classpath → `SecurityAutoConfiguration`
  - Actuator + Prometheus on classpath → `PrometheusMetricsExportAutoConfiguration`
3. **Property binding** maps YAML to `@ConfigurationProperties` objects
4. **Bean validation** runs `@Validated` on config classes
5. **Flyway** runs migrations before Hibernate validates the schema
6. **Custom beans** from `@Configuration` classes are created and wired

You can see exactly what auto-configuration was applied by running:

```bash
java -jar weather-service.jar --debug

```

This prints the "Conditions Evaluation Report" — which auto-configurations were applied and which were skipped, and why.

---

## ✅ Configuration Checklist

- \[ \] **Single `application.yml`** for base configuration with sensible defaults
- \[ \] **Profile-specific overrides** for production (database, logging, security)
- \[ \] **Environment variable placeholders** with fallback defaults: `${VAR:default}`
- \[ \] **`@ConfigurationProperties`** for type-safe, validated configuration binding
- \[ \] **`@Validated`** on config classes to fail fast on invalid configuration
- \[ \] **`hibernate.ddl-auto: validate`** — never `update` in production
- \[ \] **Graceful shutdown** enabled with timeout
- \[ \] **Actuator endpoints** secured and selectively enabled
- \[ \] **Error details hidden** in production (`include-stacktrace: never`)
- \[ \] **JaCoCo coverage gates** in the build pipeline
- \[ \] **Code formatting** enforced automatically (Spotless/Checkstyle)

---

## 🎓 Conclusion: Configuration Is Architecture

Spring Boot's configuration system does a lot of heavy lifting. The key takeaways:

1. **`@SpringBootApplication`** combines component scanning, auto-configuration, and configuration source marking in a single annotation
2. **Auto-configuration** evaluates classpath presence and property values to wire together hundreds of beans automatically
3. **The PROPS framework** (Profile-driven, Runtime Binding, Overridable defaults, Property precedence, Starter dependencies) guides configuration decisions
4. **Profile-specific YAML files** let you run the same code in dev (H2) and production (PostgreSQL) with zero code changes
5. **`@ConfigurationProperties` with `@Validated`** provides type-safe, validated configuration that fails fast at startup
6. **Environment variable overrides** (`${VAR:default}`) enable twelve-factor app configuration
7. **Starter dependencies** curate compatible libraries — add the jar, get the feature
8. **Build plugins** (JaCoCo, Spotless, Checkstyle) enforce quality as part of the build process

Configuration isn't an afterthought — it's architecture. The choices you make in YAML files and configuration classes determine how your service behaves in production, how it fails, and how quickly you can diagnose issues.

**Coming Next Week:**  
Part 3: REST Assured - Designing APIs Developers Actually Want to Use 🌐

---

📚 **Series Progress**

✅ Part 1: The Blueprint Before the Build  
✅ Part 2: Spring Boot Alchemy ← You just finished this!  
⬜ Part 3: REST Assured  
⬜ Part 4: The Data Foundation  
⬜ Part 5: When the World Breaks  
⬜ Part 6: Cache Me If You Can  
⬜ Part 7: Guarding the Gates  
⬜ Part 8: Fail Gracefully  
⬜ Part 9: 10,000 Threads and a Dream  
⬜ Part 10: Can You See Me Now?  
⬜ Part 11: Trust, But Verify  
⬜ Part 12: Ship It  
⬜ Part 13: To Production and Beyond

---

*Happy coding, and remember — the best configuration is the one that makes the right thing easy and the wrong thing hard.* ☕