DEV Community

Ankit Verma
Ankit Verma

Posted on

Recap — M2 Boot

What this module was really about

Plain Spring hands you a powerful toolbox and then asks you to assemble the workshop yourself. You pick the libraries, wire the objects together, point them at a database, choose a web server, and write the glue that starts it all. It works, but the first hour of any new project is spent on plumbing you've written a dozen times before.

Spring Boot is the layer that does that plumbing for you. It looks at what libraries you've added, guesses a sensible setup, and steps aside the moment you want to decide something yourself. Everything in module M2 was a different angle on that one promise. This recap ties the ten topics back into the single story they were always telling.

The one sentence that holds it all together

Here is the mental model to keep:

What's on your classpath, plus the settings you provide, flows through a set of conditions that decide which pre-written configuration Boot applies — and you can override any of it.

Read that again, because every topic in this module is one clause of it. Starters put things on the classpath. Externalized config supplies the settings. Conditional annotations are the conditions. Auto-configuration is the pre-written configuration. SpringApplication.run is what sets the whole flow in motion. Actuator lets you watch the result. The jar is how you ship it.

Let's walk that sentence from left to right.

Starters: how things get onto the classpath

A starter is a dependency that pulls in other dependencies. It contains almost no code of its own — it's a curated shopping list. Adding one line:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

quietly brings in Spring MVC, an embedded Tomcat, and the Jackson JSON library, all at versions known to work together. You asked for "web" and got a matched set instead of hunting down five compatible version numbers yourself.

The key thing to remember: a starter's real job is to change what's on your classpath. That matters because the classpath is the signal everything downstream reads. Which brings us to the next clause.

Conditional annotations: the "if" behind every decision

Boot ships hundreds of small configuration classes, but it doesn't blindly apply them. Each one is guarded by a condition — a rule that says "only do this if something is true."

@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
public class DataSourceAutoConfiguration {
    // configure a connection pool — but only if the conditions pass
}
Enter fullscreen mode Exit fullscreen mode

Read the two guards in plain English. @ConditionalOnClass means "only if the DataSource type is on the classpath" — i.e. only if you added a database starter. @ConditionalOnMissingBean means "only if you haven't already defined a DataSource yourself."

Those two annotations together are the whole personality of Boot. It configures things because a starter put the class there, and it backs off the instant you take over. That is what people mean when they call Boot "opinionated but polite." Hold onto this — it explains the next clause completely.

Auto-configuration: the pre-written setup that reads those conditions

Auto-configuration is the pile of guarded @Configuration classes we just met. At startup Boot gathers a long list of them and offers each one to the conditions. The ones whose conditions pass contribute beans; the rest silently do nothing.

Where does the list come from? Boot reads a plain text file baked into the library jars:

```plain text
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports





Each line is the name of one auto-configuration class. (Older Boot used `spring.factories` for the same job — same idea, different filename.) There's no magic scanning of your code and no reflection guesswork: it's a list in a file, filtered by conditions.


**The key thing to remember:** auto-configuration is not "Boot being clever." It's a fixed list of candidate configs, each one asking "do my conditions hold?" If you ever wonder _why_ a bean appeared, the honest answer is always "a class on that list had all its conditions satisfied."


## Externalized config: the settings that feed the machine


Auto-configuration still needs values — a port, a database URL, a pool size. Those live _outside_ your compiled code, in **externalized configuration**: `application.properties` or `application.yml`, environment variables, command-line arguments, and more.


The important part is **order**. When the same key is set in two places, the higher-precedence source wins. Roughly, from strongest to weakest:

- Command-line arguments (`--server.port=9000`)
- OS environment variables
- `application.properties` inside your jar

So a value baked into your jar is a _default_, and an environment variable at deploy time quietly overrides it — no rebuild required. That single rule is why the same jar can run unchanged on a laptop and in production.


## Binding: turning flat keys into real objects


Loose key–value pairs are awkward to use in code, so Boot **binds** them onto Java objects. There are two ways in.


**`@Value`** injects one key into one field:




```java
@Value("${server.port}")
private int port;
Enter fullscreen mode Exit fullscreen mode

Fine for a stray value, clumsy once you have a dozen related settings.

@ConfigurationProperties binds a whole group onto a typed object:

@ConfigurationProperties(prefix = "app.mail")
public class MailProperties {
    private String host;
    private int port;
    // getters and setters
}
Enter fullscreen mode Exit fullscreen mode

Now every app.mail.* key lands on a field automatically. And because of relaxed binding, Boot treats app.mail.host, APP_MAIL_HOST, and app.mail.HOST as the same property — so a kebab-case file and an UPPER_SNAKE environment variable both fill the same field. That's the quiet reason your properties file and your Docker environment variables agree without you doing anything.

Remember it as: @Value for one loose value, @ConfigurationProperties for a family of related ones.

Profiles: swapping settings per environment

You rarely want one set of settings everywhere. A profile is a named bundle of configuration that's only active when you switch it on.

Put shared settings in application.properties, and environment-specific ones in application-dev.properties or application-prod.properties. Activate one with a property:

```plain text
spring.profiles.active=prod





Boot layers the profile-specific file on top of the base file. You can also gate a whole bean on a profile:




```java
@Bean
@Profile("prod")
public MeterRegistry realMetrics() { ... }
Enter fullscreen mode Exit fullscreen mode

The key thing to remember: profiles are how one build carries every environment's config and picks the right slice at launch — the natural partner to the precedence order from earlier.

Starting it all: the run() method

Everything above is potential energy until one line fires it:

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}
Enter fullscreen mode Exit fullscreen mode

@SpringBootApplication is three annotations in one: it enables component scanning, marks the class as a config source, and — crucially — switches on auto-configuration. Then run does the work in order: it reads your externalized config, creates the application context (the container that holds your beans), lets auto-configuration and your own components populate it, and — for a web app — starts an embedded Tomcat inside the same process.

That embedded server is the mental shift from old Spring. There's no external server to install and deploy into; the web server is just another bean Boot started for you. Your application is the server.

Actuator: watching the thing once it runs

A running app you can't see inside is a liability. Actuator is a starter that adds ready-made HTTP endpoints reporting on the live application.

```plain text
GET /actuator/health -> {"status":"UP"}
GET /actuator/metrics -> memory, request timings, pool usage
GET /actuator/info -> build version, git commit, whatever you add





`/health` is what a load balancer or Kubernetes probe pings to decide if traffic should reach you. `/metrics` is what a monitoring system scrapes. Notice the pattern holding again: you _added a starter_, a _condition_ saw Actuator on the classpath, and _auto-configuration_ wired the endpoints. Same story, one more time.


## Packaging: the fat jar, the layered jar, and DevTools


Finally you ship it. Boot builds a **fat jar** (also called an uber jar): a single `.jar` containing your code, every dependency, _and_ the embedded server. One file runs anywhere a JVM exists:




```bash
java -jar app.jar
Enter fullscreen mode Exit fullscreen mode

No server to pre-install, no unpacking — the artifact is the whole application.

A layered jar is that same fat jar organized into layers by how often they change — dependencies in one layer, your code in another. Docker caches unchanged layers, so rebuilding after a one-line code change re-ships only your thin top layer instead of hundreds of megabytes of libraries.

And DevTools is a development-only helper that restarts the app automatically when it sees a class change, so you skip the manual stop-and-start loop. It disables itself in production, so it never ships with the fat jar.

Putting the whole sentence back together

Trace one request through the story and the module clicks into a single line:

  1. A starter puts classes on the classpath.
  2. Conditional annotations read that classpath — and your existing beans — and decide what applies.
  3. Auto-configuration contributes the beans whose conditions passed.
  4. Externalized config, bound by @ConfigurationProperties and sliced by profiles, supplies the settings those beans need.
  5. SpringApplication.run builds the context and starts embedded Tomcat.
  6. Actuator reports on the running result.
  7. A fat or layered jar ships it, with DevTools smoothing the ride while you build.

If you remember nothing else from M2, remember this: Boot is a chain of sensible defaults, each one guarded by a condition and open to your override. Once that clicks, none of its behavior looks like magic — it looks like a list being filtered by what you asked for. That is exactly the footing you want before stepping into the web layer next.

Top comments (0)