All levelsLearning path
Mastering Spring Boot
The full path from core Java to a Spring Boot 4 service you would be happy to run in production.
Spring Boot is not large because it is complicated. It is large because it has an answer for almost everything a backend service needs, and nobody tells you which of those answers you need first. The usual result is an engineer who can scaffold a controller but cannot say what the container did with it.
This path is ordered the way the understanding actually has to arrive. Java first, because Spring is a library and not a language. Then the container, because every Spring feature you will ever meet is a bean somewhere. Then Boot itself, which is mostly the container plus opinions. Everything after that is a capability you bolt on when the application needs it.
Stages run top to bottom. Where a stage branches, the nodes in it are genuinely independent — pick the one your current work needs and come back for the others. Nothing here assumes you want microservices; the single-service path is complete on its own, and stops being complete only when one deployable stops being enough.
The path
Take in orderBranches — any order
Stage 1Core Java
Spring cannot teach you Java, and most "Spring is confusing" turns out to be a gap here. These four areas are independent of each other.
4 parallel branches
Language and types
The constructs Spring code is written in, including the ones that replaced the patterns older tutorials still use.
- Classes, interfaces, generics
- Records for immutable carriers
- Sealed types and pattern matching
- Optional, used as a return type only
- equals and hashCode contracts
Learn it here
Collections and streams
The data structures every repository hands you back, and the idiom for working through them.
- List, Set, Map and when each applies
- How HashMap actually stores a key
- Streams, collectors, grouping
- Immutable and defensive copies
Learn it here
Errors and exceptions
Error design decides what your API returns when something fails, so it is a public interface concern, not an afterthought.
- Checked versus unchecked, and why Spring chose unchecked
- Designing an exception hierarchy worth catching
- try-with-resources
- What never to swallow
Learn it here
Concurrency fundamentals
A web framework is a thread pool with routing attached. You will eventually need to know whose thread you are on.
- Threads, executors, thread pools
- Virtual threads and what they change
- Shared mutable state and visibility
- CompletableFuture basics
Learn it here
Stage 2The JDK and your build
A short stage, but skipping it is why dependency problems feel like magic later.
JDK, Maven or Gradle
Spring Boot 4 sets a Java 17 baseline and is built to run on 25 — pick an LTS and know what your build is doing with it.
- Java 17 baseline, Java 21 or 25 in practice
- Maven and Gradle: the parts you actually edit
- Version management through the Spring Boot BOM
- Executable jars and what is inside one
- Generating a project with Spring Initializr
Stage 3Spring core: the container
The single highest-leverage stage on this path. Everything later is this plus configuration.
Beans and dependency injection
A bean is an object the container built and now owns. Almost every Spring question reduces to a question about that ownership.
- The ApplicationContext and bean definitions
- Constructor injection, and why not field injection
- Singleton scope and what it means for your state
- Component scanning versus explicit @Bean methods
- Qualifiers and resolving ambiguity
Learn it here
Proxies and AOP
The one mechanism that explains why @Transactional, @Cacheable and @Async all stop working when you call them from the same class.
- What a proxy is and when Spring makes one
- Self-invocation, and why it bypasses the proxy
- Aspects, pointcuts, advice
- Reading a Spring stack trace through the proxy layers
Learn it here
Stage 4Spring Boot itself
Boot is the container plus a set of defaults that back off the moment you define your own bean.
Starters and auto-configuration
Auto-configuration is conditional beans, nothing more. Once you can read a condition you can predict what Boot will do.
- Starters and the dependencies they pull in
- @ConditionalOnClass, @ConditionalOnMissingBean
- Overriding a default by declaring your own bean
- The auto-configuration report, for when it did not
Learn it here
Configuration and profiles
The same artefact has to run on a laptop and in production. Everything that differs belongs here, not in the code.
- application.yaml and the property source order
- Type-safe @ConfigurationProperties
- Profiles for environments, not for features
- Secrets through the environment, never the repo
Learn it here
Stage 5Web and REST
Four independent concerns. Controllers first; the rest in whatever order your API needs them.
4 parallel branches
Controllers and request mapping
Mapping HTTP onto methods, and keeping the HTTP shape out of everything behind them.
- REST controllers, path and query binding
- Status codes and ResponseEntity
- Content negotiation
- API versioning, now built into Spring Framework 7
- Pagination that survives a growing table
Learn it here
Validation and error responses
Rejecting a bad request well is most of what makes an API pleasant to integrate with.
- Bean Validation constraints on the request type
- @ControllerAdvice for one consistent error shape
- Problem Details (RFC 9457) responses
- Distinguishing client mistakes from your own
Learn it here
DTOs and serialization
Your JSON is a contract. Entities are not, and letting one serialise the other couples your API to your schema.
- DTOs versus entities, and the mapping between them
- Jackson: naming, nulls, dates, polymorphism
- Records as DTOs
- Lombok, and the parts of it that bite
Learn it here
Calling other services
Every outbound call is a failure mode you have adopted. Treat it as one from the first line.
- RestClient, the current synchronous client
- Declarative @HttpExchange interfaces, auto-configured in Boot 4
- Timeouts that actually fire
- Mapping an upstream response onto your own model
Learn it here
Stage 6Data and persistence
Start with mapping and transactions. Performance and migrations become urgent the moment real data arrives.
4 parallel branches
Spring Data JPA
JPA is a cache with a database behind it. Knowing which state an entity is in explains most of its surprises.
- Entities, relationships, fetch types
- The persistence context and entity lifecycle
- Repositories, derived queries, @Query
- LazyInitializationException and what it is telling you
Learn it here
Transactions
Where a transaction starts and ends is a design decision. Leaving it to @Transactional on a service method is still a decision.
- Declarative @Transactional and its proxy
- Propagation and when a new transaction is created
- Isolation levels and the anomalies they prevent
- readOnly, and rollback rules you did not intend
Learn it here
Query performance
Almost every slow Spring Boot endpoint is the same bug: one query turned into hundreds.
- The N+1 problem, and fetch joins or entity graphs
- Projections instead of whole entities
- Batch inserts and updates
- Caching, and what is safe to cache
Learn it here
Schema migrations
Hibernate generating your schema is a development convenience that has no production equivalent.
- Flyway or Liquibase as the source of truth
- Versioned, forward-only changes
- Backwards-compatible migrations for zero-downtime deploys
- Turning ddl-auto off and keeping it off
Stage 7Security
Learn the filter chain before anything else here; every later surprise is a filter ordering question.
The filter chain
Spring Security is a chain of servlet filters. Configuration you cannot explain in those terms will not behave as you expect.
- SecurityFilterChain and request matchers
- Authentication versus authorisation
- The SecurityContext and where it lives
- CSRF, CORS and session policy
Learn it here
Authentication and access control
Issuing and checking identity, and expressing who may do what in a way you can test.
- Stateless JWT-bearing resource servers
- OAuth2 and OIDC login
- Method-level @PreAuthorize rules
- Password storage, if you really must own it
Learn it here
Stage 8Testing
Not a late stage in practice — fold it in from the first endpoint. It sits here because it needs the vocabulary above.
2 parallel branches
Test slices
Booting the whole context for every test is why suites get slow. Boot gives you narrower contexts on purpose.
- Plain unit tests, with no Spring at all
- @WebMvcTest, @DataJpaTest, @JsonTest
- @SpringBootTest and when it is warranted
- Mocking a bean versus stubbing a collaborator
Learn it here
Integration testing
An in-memory database tests an in-memory database. Test against the engine you deploy on.
- Testcontainers for the real database and broker
- Boot 4 service connections, wired from the container
- WireMock for the services you do not own
- Keeping tests independent of execution order
Stage 9Observability and operations
The difference between a service you own and one you merely wrote.
Actuator and metrics
Health, metrics and configuration introspection, exposed deliberately rather than by default.
- Actuator endpoints, and locking them down
- Liveness and readiness probes that mean something
- Micrometer metrics and a meaningful custom one
- Structured logging you can actually query
Learn it here
Tracing
Once a request crosses a process boundary, logs alone stop being able to tell you where the time went.
- Micrometer Observation and spans
- Trace and span ids propagated across calls
- OpenTelemetry export
- Correlating a trace back to a log line
Stage 10Async work and messaging
Two independent answers to "this should not happen inside the request".
2 parallel branches
Async and scheduled work
In-process background work: cheap, and lost the moment the process dies.
- @Async and the executor behind it
- CompletableFuture composition
- @Scheduled jobs, and running them on one instance only
- Virtual threads for blocking workloads
Learn it here
Messaging
Durable work that outlives a restart, and the decoupling that comes with it.
- Kafka or RabbitMQ, and which problem each fits
- Producers, consumers, acknowledgement
- At-least-once delivery and idempotent consumers
- Dead letter topics and retry policy
Learn it here
Stage 11Microservices
Only worth reaching for when one deployable has become the constraint. The whole path above still applies to each service.
Service boundaries
Splitting along the wrong seam gives you a distributed system with none of the independence that justified it.
- Decomposing by business capability
- One database per service, and the consequences
- Synchronous calls versus events
- Eventual consistency and the saga pattern
Learn it here
Resilience
In one process a failed call throws. Across a network it hangs, and takes your thread pool with it.
- Retries with backoff, without amplifying an outage
- Circuit breakers and deliberate thresholds
- Bulkheads, rate limiters, concurrency limits
- Spring Framework 7 @Retryable and @ConcurrencyLimit
- Fallbacks that degrade instead of returning 500
Learn it here
Gateway and platform concerns
The things that stop being free the moment there is more than one service.
- An API gateway for routing and edge auth
- Service discovery, or stable DNS
- Centralised configuration
- Distributed tracing across the call chain
Learn it here
Stage 12Shipping it
The last stage, and the one that decides whether any of the above reaches a user.
Containers and startup
How the jar becomes something a platform can run, and what that costs at boot.
- Layered images and Cloud Native Buildpacks
- GraalVM native images, and the trade you are making
- Right-sizing the JVM in a container
- Graceful shutdown and in-flight requests
Delivery
A deploy you are nervous about is a deploy you will do rarely, which makes it riskier still.
- CI that builds, tests and publishes one artefact
- Promoting the same image through environments
- Database migrations in the deploy pipeline
- Feature flags instead of long-lived branches
Learn it here