Java is the world's most widely deployed programming language — powering Android apps, enterprise backends, big data systems, and cloud-native microservices. This roadmap shows you exactly what to learn, in what order, and realistic timelines to go from zero to a job-ready Java developer.
At a glance
| Phase |
Topics |
Time estimate |
| 1 |
Java fundamentals — syntax, types, control flow |
4–6 weeks |
| 2 |
Object-oriented programming (OOP) |
3–4 weeks |
| 3 |
Collections, Generics, Streams & Lambdas |
3–4 weeks |
| 4 |
Exception handling, I/O, and Files |
1–2 weeks |
| 5 |
Concurrency and multithreading |
3–4 weeks |
| 6 |
Build tools — Maven & Gradle |
1–2 weeks |
| 7 |
Web development — Spring Boot |
6–8 weeks |
| 8 |
Databases — JDBC, JPA, Hibernate |
4–6 weeks |
| 9 |
Testing — JUnit 5, Mockito, Testcontainers |
3–4 weeks |
| 10 |
DevOps — Docker, CI/CD, cloud deployment |
3–4 weeks |
| 11 |
System design + portfolio + job search |
4–8 weeks |
| Total to first job |
|
~12–18 months |
Phase 1 — Java fundamentals (Weeks 1–6)
Java has a verbose but readable syntax. Start here and get comfortable before jumping into frameworks.
Core concepts to master
| Concept |
Key details |
| JDK vs JRE vs JVM |
JDK = compile + run; JRE = run only; JVM = bytecode execution engine |
| Data types |
int, long, double, boolean, char + wrapper types (Integer, Long) |
| String |
Immutable; String.format(), StringBuilder for concatenation in loops |
| Arrays |
Fixed-size; int[] arr = new int[5]; prefer ArrayList in practice |
| Control flow |
if/else, switch (+ switch expressions Java 14+), for, while, do-while |
| Methods |
Return type, params, overloading, varargs (String... args) |
static |
Class-level (not instance); entry point public static void main(String[] args) |
final |
Immutable variable; non-overridable method; non-extendable class |
| Autoboxing |
int ↔ Integer conversion happens automatically |
// Hello World — the starting point
public class HelloWorld {
public static void main(String[] args) {
String name = "Java";
int year = 2025;
System.out.printf("Hello from %s in %d!%n", name, year);
}
}
Java versions that matter
| Version |
Year |
Key features |
| Java 8 |
2014 |
Lambdas, Streams, Optional, default methods, java.time |
| Java 11 |
2018 |
LTS; var (local type inference), HTTP Client, String improvements |
| Java 17 |
2021 |
LTS; sealed classes, records, pattern matching for instanceof |
| Java 21 |
2023 |
LTS; virtual threads (Project Loom), sequenced collections, record patterns |
Target Java 21 for new projects. Most enterprises run Java 11 or 17 — all three are LTS releases.
Phase 2 — Object-Oriented Programming (Weeks 7–10)
OOP is the foundation of Java design. Understand these deeply before moving to frameworks.
The four pillars
| Pillar |
Description |
Java keyword |
| Encapsulation |
Hide data; expose via getters/setters |
private, public |
| Inheritance |
Child class extends parent, reuses behaviour |
extends |
| Polymorphism |
Same interface, different behaviour at runtime |
@Override |
| Abstraction |
Expose what, hide how |
abstract class, interface |
// Clean OOP example
public abstract class Shape {
abstract double area();
public String describe() {
return "Area: " + area();
}
}
public class Circle extends Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
double area() {
return Math.PI * radius * radius;
}
}
Interface vs Abstract class
| Feature |
interface |
abstract class |
| Multiple inheritance |
Yes (implement many) |
No (extend one) |
| State |
No instance fields |
Yes |
| Default methods |
Yes (Java 8+) |
Yes |
| When to use |
Define a contract |
Share partial implementation |
Records (Java 16+)
// Replaces boilerplate POJOs
public record Point(double x, double y) {}
Point p = new Point(3.0, 4.0);
System.out.println(p.x()); // accessor auto-generated
Phase 3 — Collections, Generics, Streams & Lambdas (Weeks 11–14)
This is where Java becomes expressive. Streams and lambdas are used everywhere in modern Java.
Collections framework
| Interface |
Common implementation |
When to use |
List |
ArrayList, LinkedList |
Ordered, allow duplicates |
Set |
HashSet, TreeSet, LinkedHashSet |
No duplicates; fast lookup |
Map |
HashMap, TreeMap, LinkedHashMap |
Key-value pairs |
Queue |
ArrayDeque, PriorityQueue |
FIFO / priority ordering |
Generics
// Type-safe container
public class Box<T> {
private T value;
public Box(T value) { this.value = value; }
public T get() { return value; }
}
Box<String> box = new Box<>("hello");
String s = box.get(); // no cast needed
Lambdas and functional interfaces
// Before Java 8 — anonymous class
Comparator<String> byLength = new Comparator<>() {
@Override
public int compare(String a, String b) {
return Integer.compare(a.length(), b.length());
}
};
// With lambda
Comparator<String> byLength = (a, b) -> Integer.compare(a.length(), b.length());
// Method reference
Comparator<String> byLength = Comparator.comparingInt(String::length);
Streams API
List<String> names = List.of("Alice", "Bob", "Charlie", "Dave");
// Filter → map → collect
List<String> result = names.stream()
.filter(n -> n.length() > 3)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
// [ALICE, CHARLIE, DAVE]
// Aggregate
long count = names.stream()
.filter(n -> n.startsWith("A"))
.count(); // 1
// groupingBy
Map<Integer, List<String>> byLength = names.stream()
.collect(Collectors.groupingBy(String::length));
Optional
Optional<String> opt = Optional.ofNullable(getUserEmail());
// Bad: opt.get() without checking
// Good:
String email = opt.orElse("default@example.com");
opt.ifPresent(e -> sendWelcome(e));
String upper = opt.map(String::toUpperCase).orElse("NONE");
Phase 4 — Exception handling, I/O & Files (Weeks 15–16)
| Concept |
Key point |
| Checked exceptions |
Must handle or declare (throws); e.g., IOException |
| Unchecked exceptions |
Extend RuntimeException; optional to catch |
try-with-resources |
Auto-closes AutoCloseable (streams, connections) |
Files API |
Files.readString(), Files.writeString(), Files.walk() (Java 11+) |
// Read a file safely
try {
String content = Files.readString(Path.of("data.txt"));
System.out.println(content);
} catch (IOException e) {
System.err.println("File error: " + e.getMessage());
}
Phase 5 — Concurrency and multithreading (Weeks 17–20)
Concurrency is hard but critical for backend Java. Learn the building blocks before touching virtual threads.
Threading primitives
| Tool |
Purpose |
Thread / Runnable |
Low-level; rarely used directly |
ExecutorService |
Thread pool management |
Callable + Future |
Task that returns a result |
CompletableFuture |
Async/reactive pipelines |
synchronized |
Intrinsic lock on a monitor |
ReentrantLock |
Explicit lock with try/unlock |
volatile |
Visibility guarantee; no atomicity |
AtomicInteger |
Lock-free atomic counter |
// Thread pool + CompletableFuture
ExecutorService executor = Executors.newFixedThreadPool(4);
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> fetchUser(userId), executor)
.thenApply(user -> "Hello, " + user.name())
.exceptionally(ex -> "Error: " + ex.getMessage());
String result = future.get(); // blocks; use join() in chains
executor.shutdown();
Virtual threads (Java 21)
// Millions of cheap threads — no more thread pool tuning
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
executor.submit(() -> {
Thread.sleep(Duration.ofMillis(100)); // blocks cheaply
return processRequest();
});
}
}
Phase 6 — Build tools: Maven & Gradle (Weeks 21–22)
| Feature |
Maven |
Gradle |
| Config format |
XML (pom.xml) |
Groovy/Kotlin DSL (build.gradle) |
| Build speed |
Slower |
Faster (incremental, caching) |
| Convention |
Strict (over configuration) |
Flexible |
| Spring Boot default |
Yes |
Yes (Kotlin DSL preferred) |
Essential Maven commands
mvn clean install # clean + compile + test + package
mvn test # run tests only
mvn package -DskipTests # build JAR without tests
mvn dependency:tree # show dependency tree
mvn versions:display-dependency-updates # check for updates
Phase 7 — Web development with Spring Boot (Weeks 23–30)
Spring Boot is the dominant Java web framework. Start here for backend APIs.
Key concepts
| Concept |
Description |
| IoC container |
Spring creates and wires beans for you |
@Component / @Service / @Repository |
Stereotype annotations → auto-detected beans |
@Autowired / constructor injection |
Inject dependencies (prefer constructor injection) |
@RestController |
Combines @Controller + @ResponseBody |
@RequestMapping / @GetMapping |
Map HTTP routes to methods |
application.properties / application.yml |
Externalise configuration |
| Spring profiles |
dev, prod, test configs |
// Complete REST endpoint
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public UserDto createUser(@Valid @RequestBody CreateUserRequest req) {
return userService.create(req);
}
}
Spring ecosystem map
| Library |
Purpose |
| Spring Web (MVC) |
REST APIs, MVC web apps |
| Spring Data JPA |
Repository pattern on top of Hibernate |
| Spring Security |
Auth, JWT, OAuth2 |
| Spring Boot Actuator |
Health checks, metrics, /actuator endpoints |
| Spring Boot Test |
Integration testing with @SpringBootTest |
| Spring Validation |
@Valid, @NotNull, @Size, custom validators |
| Spring Cache |
@Cacheable, Redis integration |
| Spring WebFlux |
Reactive non-blocking APIs (advanced) |
Phase 8 — Databases: JDBC, JPA & Hibernate (Weeks 31–36)
| Layer |
Tool |
Use case |
| Raw SQL |
JDBC |
Full control; verbose |
| Query builder |
jOOQ |
Type-safe SQL in Java |
| ORM |
Hibernate / JPA |
Object-relational mapping |
| Repository |
Spring Data JPA |
CRUD + query methods auto-generated |
Spring Data JPA example
// Entity
@Entity
@Table(name = "users")
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Order> orders = new ArrayList<>();
}
// Repository — no implementation needed
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
List<User> findByCreatedAtAfter(LocalDate date);
@Query("SELECT u FROM User u WHERE u.email LIKE %:domain%")
List<User> findByEmailDomain(@Param("domain") String domain);
}
Common JPA pitfalls
| Problem |
Cause |
Fix |
| N+1 queries |
Lazy loading in a loop |
Use @EntityGraph or JOIN FETCH |
| LazyInitializationException |
Accessing lazy collection outside session |
Use @Transactional or DTOs |
| Cascading too broadly |
CascadeType.ALL on @ManyToMany |
Be explicit about cascade operations |
| Missing indexes |
No @Index or DB migration |
Add indexes on FK and query columns |
Phase 9 — Testing (Weeks 37–39)
Testing pyramid
| Level |
Tool |
Speed |
Scope |
| Unit |
JUnit 5 + Mockito |
Fast |
Single class |
| Integration |
@SpringBootTest, Testcontainers |
Medium |
Multiple layers |
| E2E |
REST Assured, Selenium |
Slow |
Full stack |
// Unit test with Mockito
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock UserRepository userRepository;
@InjectMocks UserService userService;
@Test
void findById_returnsUser_whenExists() {
User user = new User(1L, "alice@example.com");
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
Optional<User> result = userService.findById(1L);
assertThat(result).isPresent();
assertThat(result.get().getEmail()).isEqualTo("alice@example.com");
}
}
// Integration test with real DB
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerIT {
@Autowired MockMvc mockMvc;
@Test
void getUser_returns404_whenNotFound() throws Exception {
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isNotFound());
}
}
Testcontainers for real database testing
@Testcontainers
@SpringBootTest
class RepositoryIT {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16")
.withDatabaseName("testdb");
@DynamicPropertySource
static void properties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
}
Phase 10 — DevOps: Docker, CI/CD, cloud (Weeks 40–43)
Multi-stage Dockerfile for Spring Boot
# Build stage
FROM eclipse-temurin:21-jdk-alpine AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN ./mvnw clean package -DskipTests
# Runtime stage
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
GitHub Actions CI pipeline
name: Java CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Cache Maven packages
uses: actions/cache@v4
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
- run: mvn clean verify
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
Cloud deployment options
| Platform |
Best for |
Key service |
| AWS |
Enterprise; fine-grained control |
ECS, EKS, Elastic Beanstalk |
| GCP |
Google Cloud ecosystem |
Cloud Run, GKE |
| Azure |
Microsoft stack |
Azure App Service, AKS |
| Railway |
Simple deploys, hobby projects |
Automatic Dockerfile deploy |
| Render |
Easy cloud PaaS |
Web Services with zero config |
Full technology map
┌─────────────────────────────────────────────────────────────────────┐
│ JAVA DEVELOPER ROADMAP │
├──────────────────────┬──────────────────────┬───────────────────────┤
│ CORE LANGUAGE │ WEB & FRAMEWORKS │ INFRASTRUCTURE │
│ │ │ │
│ Java 21 (LTS) │ Spring Boot 3 │ Maven / Gradle │
│ OOP + SOLID │ Spring MVC │ Docker │
│ Generics │ Spring Security │ PostgreSQL / MySQL │
│ Collections │ Spring Data JPA │ Redis │
│ Streams + Lambdas │ Hibernate │ RabbitMQ / Kafka │
│ Concurrency │ REST APIs │ AWS / GCP / Azure │
│ Virtual threads │ WebFlux (reactive) │ Kubernetes │
│ │ │ │
├──────────────────────┼──────────────────────┼───────────────────────┤
│ TESTING │ TOOLING │ ADVANCED │
│ │ │ │
│ JUnit 5 │ IntelliJ IDEA │ System design │
│ Mockito │ Git │ Microservices │
│ Testcontainers │ GitHub Actions │ gRPC │
│ REST Assured │ SonarQube │ Event sourcing │
│ AssertJ │ Postman / Bruno │ CQRS │
└──────────────────────┴──────────────────────┴───────────────────────┘
Realistic 12-month timeline
| Month |
Focus |
Milestone |
| 1–2 |
Java syntax, types, control flow |
Write CLI programs |
| 3 |
OOP — classes, inheritance, interfaces |
Design a small class hierarchy |
| 4 |
Collections, Streams, Lambdas |
Process data without loops |
| 5 |
Concurrency basics + build tools |
Multi-threaded word counter |
| 6 |
Spring Boot REST API |
CRUD API with in-memory data |
| 7 |
Databases — JPA, Hibernate |
CRUD API backed by PostgreSQL |
| 8 |
Spring Security + testing |
Secured API with 80%+ coverage |
| 9 |
Docker + CI/CD pipeline |
Containerised app deploying automatically |
| 10 |
System design + cloud deploy |
App live on Railway or Render |
| 11–12 |
Portfolio projects + interview prep |
2–3 projects on GitHub + 50 LeetCode Easys |
Portfolio projects
| Project |
Skills demonstrated |
Why employers care |
| REST Task API |
Spring Boot, JPA, Postgres, JWT, tests |
Core backend pattern |
| E-commerce backend |
Products, orders, payments, roles, caching |
Complex domain model |
| Chat app |
WebSockets, event-driven, concurrency |
Real-time systems |
| Job board |
Search, pagination, file uploads, email |
Full-featured CRUD |
| Microservices blog |
Multiple services, API gateway, Docker Compose |
Architecture knowledge |
| Batch data processor |
Spring Batch, large datasets, scheduling |
Enterprise Java |
Java developer roles and salaries (2025)
| Role |
Tech focus |
Avg salary (US) |
| Junior Java Developer |
Spring Boot, SQL, REST |
$65k–$90k |
| Mid Java Backend Developer |
Microservices, JPA, testing, CI/CD |
$90k–$130k |
| Senior Java Engineer |
Architecture, system design, performance |
$130k–$180k |
| Java Architect |
Distributed systems, org-wide standards |
$160k–$220k+ |
| Android Developer |
Kotlin/Java, Android SDK, Jetpack |
$90k–$150k |
| Data Engineer (Java) |
Spark, Kafka, Flink on JVM |
$110k–$160k |
Common mistakes
| Mistake |
Why it hurts |
What to do instead |
| Skipping fundamentals for Spring Boot |
Can't debug framework errors |
Master OOP + Collections first |
Using null everywhere |
NullPointerException in production |
Use Optional and fail-fast validation |
| Mutable public fields |
Breaks encapsulation |
private fields + constructor/getters |
Catching Exception broadly |
Swallows bugs silently |
Catch specific exceptions |
| N+1 queries in JPA |
Slow API under load |
Use JOIN FETCH or @EntityGraph |
No @Transactional on write operations |
Partial DB writes on failure |
Annotate all service write methods |
| Testing only happy paths |
Misses edge cases |
Test nulls, empty inputs, boundary values |
| Ignoring Java versions |
Using deprecated APIs |
Target Java 17 or 21 LTS |
Java vs related backend languages
| Feature |
Java |
Kotlin |
Python |
Go |
Node.js |
| Type system |
Static, verbose |
Static, concise |
Dynamic |
Static |
Dynamic |
| Performance |
High (JIT) |
High (JIT) |
Moderate |
Very high |
Moderate |
| Startup time |
Slow (improves with GraalVM) |
Slow |
Fast |
Very fast |
Fast |
| Android |
Yes (legacy) |
Yes (primary) |
No |
No |
No |
| Enterprise adoption |
Very high |
Growing |
High (ML/web) |
High (cloud) |
High (web) |
| Learning curve |
Steep (verbose) |
Moderate |
Easy |
Moderate |
Easy |
| Spring Boot |
Yes |
Yes |
No |
No |
No |
FAQ
How long does it take to become a Java developer?
With consistent daily study (2–3 hours), most people are job-ready in 12–18 months. A CS degree or existing programming experience can cut this to 6–9 months.
Should I learn Java or Kotlin in 2025?
For Android, learn Kotlin — it's the official language and Google-preferred. For backend / Spring Boot, both work equally well. Java has more job postings; Kotlin is more pleasant to write. If starting from scratch, Kotlin saves boilerplate. If you know Java already, the switch to Kotlin takes 2–4 weeks.
Is Java still in demand in 2025?
Yes. Java consistently ranks in the top 3 languages on Stack Overflow surveys, TIOBE, and GitHub usage. It dominates enterprise backends, Android (legacy), big data (Hadoop/Spark), and finance.
Do I need to know Spring to get a Java job?
Almost always yes. Over 80% of Java backend job postings mention Spring or Spring Boot. Learn it by week 23 of this roadmap.
Should I use Maven or Gradle?
Maven is simpler to start with and still widely used in enterprise. Gradle is faster and more flexible. Spring Initializr supports both — pick Maven for your first project, then learn Gradle basics.
What's the difference between JDK 17 and JDK 21?
Both are LTS. Java 21 adds virtual threads (Project Loom) — a major change that makes writing concurrent code far simpler and removes the need for reactive programming in most cases. Use 21 for new projects; you'll encounter 17 and 11 in existing codebases.