Skip to content

Spring Boot Learning Roadmap: From Intermediate to Employable in 90 Days

Purpose

I wasted 6 months before my first Spring Boot job. I jumped between microservices, Kafka, Kubernetes — stuff I never needed on day one. I should have focused on the core 20% that gets you 80% of the way.

This post is the roadmap I wish I had. Three phases, 30 days each. By day 90, you should be ready to apply and pass technical interviews.

The Big Mistake I Made

When I started learning Spring Boot, I tried to learn everything at once:

Learning plan I had vs what worked
What I tried: What I should have done:
├── Spring Cloud ├── JPA + REST
├── Kafka ├── Spring Security basics
├── Kubernetes ├── Testing (unit + integration)
├── Microservices patterns ├── Redis (caching only)
└── Event sourcing └── Docker (just enough to deploy)

All that advanced stuff matters later. But for the first job, nobody asks you to design an event-driven microservices mesh. They want to know: can you build a CRUD API, secure it, test it, and deploy it?

The 90-Day Plan Overview

Three phases, 30 days each
Phase 1: Project Core → Days 1-30 → JPA, REST, Security, Testing
Phase 2: Production → Days 31-60 → Redis, Scheduling, Docker, Deploy
Phase 3: Job Prep → Days 61-90 → Swagger, Architecture review, Mock interviews, Apply

Each phase builds on the previous one. By the end of Phase 1, you have a working API. By the end of Phase 2, it’s deployable. By the end of Phase 3, you can talk about it in an interview.

Three-phase roadmap timeline: Phase 1 Project Core (Days 1-30) building a working API, Phase 2 Production Polish (Days 31-60) adding caching, Docker, and deployment, Phase 3 Job Prep (Days 61-90) covering Swagger, mock interviews, and applications

Phase 1: Project Core (Days 1-30)

This phase is about building a single, working Spring Boot application with a database.

Week 1: Project Setup and JPA

  • Generate a project with Spring Initializr
  • Add Spring Web, Spring Data JPA, PostgreSQL driver, Lombok
  • Create 2-3 entities with relationships
  • Write repository methods using @Query

Week 2: REST API

  • Build CRUD endpoints for your entities
  • Use @RestController, @Service, @Repository layers
  • Handle exceptions with @ControllerAdvice
  • Validate input with @Valid

Week 3: Spring Security

  • Add Spring Security dependency
  • Implement JWT-based authentication
  • Configure role-based access control
  • Test all endpoints with different roles

Week 4: Testing

  • Write unit tests with JUnit 5 and Mockito
  • Write integration tests with @SpringBootTest
  • Use @DataJpaTest for repository tests
  • Mock external calls with Mockito

Deliverable: A working blog API or todo app with JWT auth, tested endpoints, and a PostgreSQL database.

One rule I set for myself: no AI coding assistants for the first 30 days. Every line typed manually. Every error debugged alone. This builds real understanding.

Phase 2: Production Polish (Days 31-60)

Now you make your API production-ready.

Week 5: Caching with Redis

  • Run Redis via Docker
  • Add @Cacheable, @CacheEvict annotations
  • Cache frequently queried data
  • Handle cache invalidation

Week 6: Scheduling and Async

  • Use @Scheduled for periodic tasks
  • Use @Async for long-running operations
  • Configure thread pools

Week 7: Dockerize the App

  • Write a Dockerfile for your Spring Boot app
  • Write a docker-compose.yml with your app + PostgreSQL + Redis
  • Test the full stack locally with Docker Compose

Week 8: Deploy to a Server

  • Set up a VPS (DigitalOcean, Linode, or Hetzner)
  • Deploy via Docker Compose
  • Set up Nginx reverse proxy
  • Add HTTPS with Certbot

Deliverable: Your API runs on a real server, accessible via HTTPS, with caching and scheduled tasks.

Deployment architecture diagram showing Spring Boot app, PostgreSQL, and Redis containers managed by Docker Compose, behind an Nginx reverse proxy with HTTPS via Certbot, deployed on a VPS

Phase 3: Job Prep (Days 61-90)

The technical part is done. Now you need to pass interviews.

Week 9: API Documentation

  • Add Springdoc OpenAPI (Swagger UI)
  • Write meaningful API descriptions
  • Add request/response examples

Week 10: Architecture Deep Dive

  • Review your own code from Phase 1
  • Understand why you made each decision
  • Study common interview topics: DI, AOP, transaction propagation, isolation levels

Week 11: Mock Interviews

  • Record yourself explaining your project
  • Practice answering “Why did you choose X instead of Y?”
  • Prepare for system design questions at your level (not senior architect)

Week 12: Apply

  • Polish your GitHub README
  • Update your resume with the project
  • Apply to 5-10 positions per week
  • Tailor each application

Deliverable: Resume with a live project, Swagger docs, and interview-ready explanations.

Anti-Pattern vs Pattern: JWT Filter Example

Here is something I got wrong many times — how to write a JWT filter. The anti-pattern is common among beginners.

Anti-Pattern: Spaghetti Filter

JwtFilter.java (anti-pattern)
@Component
public class JwtFilter extends OncePerRequestFilter {
// mixing concerns: parsing, validation, and auth logic all here
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
String jwt = token.substring(7);
try {
// parsing JWT inline
Claims claims = Jwts.parser()
.setSigningKey("my-secret-key".getBytes())
.parseClaimsJws(jwt)
.getBody();
String username = claims.getSubject();
// setting SecurityContext inline
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(
username, null, List.of()
);
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (Exception e) {
// swallow or rethrow — unclear
}
}
chain.doFilter(request, response);
}
}

The problem: everything is in one method. Parsing, validation, context setup — no separation. If you change your JWT library, you rewrite the filter. If the token is expired, the error handling is vague.

Pattern: Clean Separation

JwtTokenProvider.java (pattern)
@Component
public class JwtTokenProvider {
private final String secretKey;
public JwtTokenProvider(@Value("${jwt.secret}") String secretKey) {
this.secretKey = secretKey;
}
public String getUsernameFromToken(String token) {
return Jwts.parser()
.setSigningKey(secretKey.getBytes())
.parseClaimsJws(token)
.getBody()
.getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey.getBytes())
.parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}
JwtFilter.java (pattern)
@Component
@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {
private final JwtTokenProvider tokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String token = resolveToken(request);
if (token != null && tokenProvider.validateToken(token)) {
String username = tokenProvider.getUsernameFromToken(token);
setSecurityContext(username);
}
chain.doFilter(request, response);
}
private String resolveToken(HttpServletRequest request) {
String bearer = request.getHeader("Authorization");
if (bearer != null && bearer.startsWith("Bearer ")) {
return bearer.substring(7);
}
return null;
}
private void setSecurityContext(String username) {
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(
username, null, List.of()
);
SecurityContextHolder.getContext().setAuthentication(auth);
}
}

The pattern separates concerns: token provider handles parsing and validation, filter handles HTTP concerns. Each class has one reason to change.

Why the 90-Day Approach Works

I learned this the hard way — trying to study everything makes you learn nothing. The 90-day plan forces constraint:

  • Phase 1 locks in the fundamentals before you touch advanced topics. You can’t build on a shaky foundation.
  • Phase 2 gives you real-world experience. Local code is different from running code. Caching, deployment, HTTPS — these are what employers actually pay for.
  • Phase 3 shifts you from learner to candidate. The last month is purely about packaging yourself for the job market.

The 30-day-per-phase cadence also creates urgency. You can’t spend two weeks tweaking your Dockerfile. You ship it and move on.

The Week-by-Week Checklist

Checklist template you can copy
Week 1 [] Generate Spring Boot project with JPA
Week 1 [] Create entities and repositories
Week 2 [] Build CRUD REST endpoints
Week 2 [] Add exception handling and validation
Week 3 [] Implement JWT authentication
Week 3 [] Add role-based access control
Week 4 [] Write unit tests for services
Week 4 [] Write integration tests for controllers
Week 5 [] Add Redis caching
Week 5 [] Test cache behavior
Week 6 [] Add scheduled tasks
Week 6 [] Add async methods
Week 7 [] Dockerize the application
Week 7 [] Set up Docker Compose (app + DB + Redis)
Week 8 [] Deploy to a VPS
Week 8 [] Set up Nginx and HTTPS
Week 9 [] Add Swagger documentation
Week 10 [] Review architecture decisions
Week 11 [] Practice mock interviews
Week 12 [] Apply to jobs

Print this, put it on your wall, check each box before moving on.

Summary

In this post, I laid out a 90-day Spring Boot roadmap from intermediate to job-ready. Three phases — project core, production polish, job prep — each with weekly deliverables. The key point is that you don’t need to learn everything before your first job. You just need to build one solid API, deploy it, and be able to explain it.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments