Week 1: Java Foundations & Environment Setup

Spring Boot is Java underneath, and this course assumes you can already write basic classes and use collections — but not much more. This week refreshes the modern Java features Spring Boot code leans on constantly (records, sealed classes, pattern matching), gets JDK 21 and a build tool installed properly, and ends with your first Spring Boot application running locally.

Module 1 of 12 Week 1 of 15 ~3–4 Hours Hands-on Exercise Included

By the end of this week, you'll be able to

  • Read and write modern Java: records, sealed classes and pattern matching
  • Set up JDK 21 and a build tool (Maven or Gradle) with a clean project structure
  • Generate, run and understand your first Spring Boot application

1. Modern Java Essentials

Spring Boot 3 code — and this course's example code — leans on three Java features added since Java 8 that are easy to miss if your Java knowledge predates them: records, sealed classes, and pattern matching. None of them are Spring-specific, but you'll see all three constantly once DTOs and typed responses show up from Week 3 onward.

Records

A record is an immutable data carrier — it generates a constructor, getters, equals(), hashCode() and toString() from a one-line field list, replacing pages of boilerplate.

CustomerDto.java
public record CustomerDto(Long id, String name, String email) {}

// Usage -- everything below is generated for you
CustomerDto dto = new CustomerDto(1L, "Ada Lovelace", "ada@example.com");
dto.name();        // "Ada Lovelace" -- accessor, not getName()
dto.equals(other);  // structural equality, field by field
System.out.println(dto);   // CustomerDto[id=1, name=Ada Lovelace, email=ada@example.com]

Sealed classes

A sealed class or interface restricts which other classes may extend or implement it, listed explicitly with permits. Combined with pattern matching, this lets the compiler verify you've handled every possible case.

PaymentResult.java
public sealed interface PaymentResult
    permits PaymentResult.Success, PaymentResult.Declined {

    record Success(String transactionId) implements PaymentResult {}
    record Declined(String reason) implements PaymentResult {}
}

Pattern matching for switch

Modern switch can match on a value's type (and even destructure a record's fields directly), replacing a chain of instanceof checks and casts with one readable block:

PaymentHandler.java
String describe(PaymentResult result) {
    return switch (result) {
        case PaymentResult.Success(String txId) ->
            "Payment succeeded: " + txId;
        case PaymentResult.Declined(String reason) ->
            "Payment declined: " + reason;
    };
}
// No default branch needed -- the compiler knows Success and Declined
// are the only two permitted implementations, and enforces exhaustiveness.
Why this matters for Spring Boot

You'll write records constantly as request/response DTOs starting Week 3, and this sealed-interface-plus-switch pattern is exactly how you'll model "this API call either succeeded or failed in one of a few specific ways" throughout the course.

2. JDK 21 & Build Tools

This course uses Java 21, the current LTS (Long-Term Support) release — the version most production Spring Boot 3 services actually run on. Install it, then confirm it's active:

terminal
java -version
# openjdk version "21.0.x" ...

javac -version
# javac 21.0.x

A build tool compiles your code, resolves and downloads dependencies (like Spring Boot's own libraries), and packages the result. Spring supports two: Maven (XML-configured, more explicit) and Gradle (Groovy/Kotlin DSL, more concise). This course uses Maven for its examples because its verbosity makes what's happening easier to see the first time — but everything transfers directly to Gradle.

pom.xml — the essentials
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.codeverse</groupId>
    <artifactId>week-01</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.0</version>
    </parent>

    <properties>
        <java.version>21</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

The spring-boot-starter-parent is what makes Spring Boot's dependency management work — it pins compatible versions of every Spring library so you don't have to track them yourself. A starter like spring-boot-starter-web is just a curated bundle of dependencies for one job (here, building web APIs); you'll add more starters as the course goes on.

Fastest way to start a project

Don't hand-write a pom.xml from scratch. Use start.spring.io (Spring Initializr) to generate a working project with exactly the dependencies you pick — it's what every example in this course is built from.

3. Project Structure & Dependency Management

Every Spring Boot project generated by Initializr follows the same Maven-standard layout. Knowing it cold means you'll never have to guess where a file belongs:

project layout
week-01/
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/codeverse/week01/
│   │   │       └── Week01Application.java   ← entry point
│   │   └── resources/
│   │       └── application.properties        ← config
│   └── test/
│       └── java/
│           └── com/codeverse/week01/
│               └── Week01ApplicationTests.java

src/main/java is your application code; src/main/resources holds configuration and static files; src/test/java mirrors the main package structure for tests. This convention-over-configuration layout is one reason Spring Boot projects look similar across companies — you'll recognize it in Week 8's testing module and every week after.

4. Your First Spring Boot Application

A Spring Boot app starts from one class annotated @SpringBootApplication — a single annotation that bundles three things: component scanning, auto-configuration, and marking this as the app's configuration root. You'll unpack exactly what each of those does in Week 2; for now, treat it as "the switch that turns this class into a runnable Spring app."

Week01Application.java
package com.codeverse.week01;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class Week01Application {

    public static void main(String[] args) {
        SpringApplication.run(Week01Application.class, args);
    }
}

@RestController
class HelloController {

    @GetMapping("/hello")
    String hello() {
        return "Hello from Spring Boot on Java 21!";
    }
}

SpringApplication.run() boots an embedded web server (Tomcat, by default), scans your package for components like HelloController, and wires everything together — all before your terminal prints its first log line. You'll build real @RestController classes properly in Week 3; this one exists just to prove the whole chain works end to end.

5. Running, Packaging & IDE Workflow

You can run a Spring Boot app straight from Maven, without packaging anything first — the fastest loop while you're actively coding:

terminal
./mvnw spring-boot:run

# Then, in another terminal:
curl http://localhost:8080/hello
# Hello from Spring Boot on Java 21!

For anything beyond local development, you package the app into a single executable JAR — no separate application server required, since Spring Boot embeds one:

terminal
./mvnw clean package
java -jar target/week-01-0.0.1-SNAPSHOT.jar

That single JAR is what you'll containerize with Docker in Week 13 — the exact same artifact runs locally and in production, which is a large part of why Spring Boot displaced older, application-server-based Java deployment models.

IDE recommendation

IntelliJ IDEA (the free Community Edition is enough for this course) has first-class Spring Boot support — run configurations, endpoint discovery, and a built-in HTTP client for testing routes like /hello without leaving the editor.

6. Hands-on Exercise

Hands-on

Generate, run and extend your first Spring Boot service

Get the full local loop working, then apply this week's modern-Java features to a small typed response.

Requirements:

  1. Generate a project at start.spring.io with Java 21, Maven, and the "Spring Web" dependency. Import it into your IDE.
  2. Confirm it runs with ./mvnw spring-boot:run and that GET / or a route you add responds over curl or your browser.
  3. Define a record Greeting(String message, int wordCount).
  4. Add a GET /greet?name=... endpoint returning a Greeting record (Spring will serialize it to JSON automatically) — have it default to "world" if no name is supplied.
  5. Model a sealed interface GreetResult with Ok(Greeting greeting) and Invalid(String reason) variants; return Invalid when name is blank or over 50 characters, and use a pattern-matching switch to turn the result into the right HTTP response.
Hint

You don't need a proper JSON error-response shape yet — that's Week 3's @ControllerAdvice topic. Returning a plain string for the Invalid case with the right status code is enough for this exercise.

7. Knowledge Check

Four quick questions. Expand each to check your answer.

Q1

What does declaring public record CustomerDto(Long id, String name) give you for free?

A constructor taking both fields, field accessors (id() and name(), not getId()), and structurally-correct equals(), hashCode() and toString() implementations — all generated by the compiler from that one line, with the fields implicitly final and immutable.

Q2

Why can a pattern-matching switch over a sealed interface skip a default branch?

A sealed type's permits clause tells the compiler the complete, closed set of implementations that can ever exist. If the switch covers every permitted case, the compiler knows no other case is possible and doesn't require a fallback — and if you add a new permitted type later without updating the switch, compilation fails instead of silently falling through.

Q3

What does spring-boot-starter-parent actually do in pom.xml?

It's a parent POM that pins a compatible set of dependency versions across the entire Spring ecosystem, so when you add a starter like spring-boot-starter-web you don't specify a version yourself and risk pulling in mismatched library versions. It also supplies default plugin configuration, like the plugin that builds the executable JAR.

Q4

Why doesn't a Spring Boot app need a separately installed application server like Tomcat?

Spring Boot embeds a servlet container (Tomcat by default) directly inside the executable JAR that ./mvnw clean package produces. Running java -jar app.jar starts the app and its web server together as one process, instead of deploying a WAR file into a separately managed server — this is what makes "the same artifact runs identically locally and in production" true.