A badly prepared environment is the number one source of frustration when starting out with Spring Boot: compilation errors that look like framework problems but come from the Java version, dependencies that will not download, or an IDE flagging a perfectly valid project in red. This lesson walks you through getting your machine ready in a verifiable way: installing and checking JDK 21, understanding the choice of Maven and its wrapper, picking an IDE, having tools to exercise the API and getting Docker ready for the advanced modules. We will finish with a checklist you can run as it stands.

Contents

  1. JDK 21: installation and verification
  2. Managing several Java versions with SDKMAN!
  3. Maven versus Gradle, and why this course uses Maven
  4. The Maven wrapper (mvnw)
  5. Choosing an IDE: IntelliJ IDEA, Eclipse/STS and VS Code
  6. Tools for testing the API
  7. Docker: a requirement for the later modules
  8. Final verification checklist
  9. Common Mistakes and Tips
  10. Exercises

  1. JDK 21: installation and verification

Spring Boot 3 requires Java 17 or higher. We will use Java 21, the most recent LTS (long-term support) release and one widely adopted in production.

You need a JDK (Java Development Kit), not just a JRE: the JDK includes the javac compiler, without which Maven cannot build anything.

Available distributions

They all implement the same standard; the difference lies in support and packaging.

Distribution Vendor Notes
Eclipse Temurin Adoptium The recommended default: free, no registration, cross-platform
Amazon Corretto AWS A good fit if you deploy on AWS; long support
Azul Zulu Azul Systems Broad platform coverage
Oracle JDK Oracle Licence with conditions; unnecessary for learning
The distro's OpenJDK Debian, Ubuntu, Fedora Convenient on Linux, version tied to the distribution

Linux

On Debian- or Ubuntu-based distributions:

# Refresh the indexes and install JDK 21 from the repositories
sudo apt update
sudo apt install openjdk-21-jdk

# Check the installation
java -version
javac -version

On Fedora or Red Hat derivatives:

sudo dnf install java-21-openjdk-devel
java -version

macOS

With Homebrew:

brew install --cask temurin@21

# Check
java -version

# List every JDK installed on the system
/usr/libexec/java_home -V

Windows

Download the Eclipse Temurin 21 .msi installer from adoptium.net and, during installation, tick the "Set JAVA_HOME variable" option. Afterwards, in PowerShell:

java -version
javac -version
echo $env:JAVA_HOME

Reading the output

A correct installation produces something like this:

$ java -version
openjdk version "21.0.5" 2024-10-15 LTS
OpenJDK Runtime Environment Temurin-21.0.5+11 (build 21.0.5+11-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.5+11 (build 21.0.5+11-LTS, mixed mode)

Three things to look at:

  • 21.0.5: the major version is 21. That is the only critical part.
  • 64-Bit Server VM: it is a 64-bit JVM, which is what you want.
  • javac -version must exist and match. If java responds but javac says "command not found", you have installed a JRE or the -devel/-jdk package is missing.

The JAVA_HOME variable

Maven and many IDEs locate the JDK through JAVA_HOME. To check it:

# Linux / macOS
echo $JAVA_HOME
# Should print something like /usr/lib/jvm/java-21-openjdk-amd64

# If it is empty, set it in your ~/.bashrc or ~/.zshrc
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH="$JAVA_HOME/bin:$PATH"

  1. Managing several Java versions with SDKMAN!

Keeping projects on Java 8, 17 and 21 at the same time is common. SDKMAN! lets you switch version with a single command, without touching system variables. It works on Linux and macOS (on Windows, through WSL or Git Bash).

# Install SDKMAN!
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"

# List the available Java versions
sdk list java

# Install Temurin 21
sdk install java 21.0.5-tem

# Use that version in the current shell only
sdk use java 21.0.5-tem

# Make it the system default
sdk default java 21.0.5-tem

# Check which one is active
sdk current java

A very handy detail: SDKMAN! recognises a .sdkmanrc file at the root of the project.

# .sdkmanrc at the root of ciclourbana
java=21.0.5-tem
maven=3.9.9

Run sdk env inside that folder and the shell switches automatically to the declared versions. It is the cleanest way to guarantee that the whole team compiles with the same toolchain.

SDKMAN! also installs Maven, Gradle and other tools:

sdk install maven
sdk list maven

  1. Maven versus Gradle, and why this course uses Maven

Both are build tools: they download dependencies, compile, run tests and package. Spring Boot supports the two equally well.

Criterion Maven Gradle
Build file pom.xml (declarative XML) build.gradle / build.gradle.kts (Groovy or Kotlin)
Learning curve Low: rigid, predictable structure Medium-high: it is a programming language
Verbosity High Low
Build speed Good Better: task caching and incremental compilation
Flexibility Limited, convention-driven Very high, arbitrary scripts
Documentation and examples Predominant in the Spring world Plentiful, but less common in tutorials
Typical use Enterprise applications Android, large or multi-module projects

This course uses Maven for three practical reasons:

  1. The pom.xml is declarative and explicit: it reads top to bottom and hides no logic. While learning, that matters more than speed.
  2. The vast majority of Spring documentation, and of the answers you will come across, uses Maven.
  3. It is Spring Initializr's default, the tool you will use to create the project in lesson 01-03.

If your company uses Gradle, everything you learn here carries over almost literally: what changes is the syntax of the build file and the names of the tasks, not the concepts.

Checking Maven

mvn -version

Expected output:

Apache Maven 3.9.9
Maven home: /home/user/.sdkman/candidates/maven/current
Java version: 21.0.5, vendor: Eclipse Adoptium

Look at the last line: Maven reports which JDK it is using. If it says Java version: 17, Maven is not seeing your JDK 21 even though java -version does; check JAVA_HOME.

  1. The Maven wrapper (mvnw)

Here comes some good news: you do not need to install Maven to follow this course.

Spring Initializr generates a wrapper in the project: two scripts (mvnw for Linux/macOS and mvnw.cmd for Windows) plus a .mvn/wrapper folder with the configuration. The first time you run it, the wrapper downloads the exact Maven version the project declares and uses it.

# Instead of: mvn clean package
./mvnw clean package

# On Windows (PowerShell or CMD)
mvnw.cmd clean package

The benefits are significant:

  • Reproducibility: the whole team and the continuous integration server use the same Maven version, with no coordination required.
  • Zero installation: someone clones the repository and builds with nothing installed beyond the JDK.
  • Versioning: the Maven version is upgraded by changing a file and reviewed like any other code change.

The specific version lives here:

# .mvn/wrapper/maven-wrapper.properties
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip

Throughout this course we will always use ./mvnw. If you have Maven installed, mvn will work just the same, but get used to the wrapper: it is the professional convention.

A common snag on Linux/macOS: if ./mvnw reports "permission denied" after cloning a repository, the execute bit is missing.

chmod +x mvnw

  1. Choosing an IDE: IntelliJ IDEA, Eclipse/STS and VS Code

You can follow the course with any of the three. This is what each one brings:

IDE Free edition Strengths Drawbacks
IntelliJ IDEA Community (enough for the course) The best Java completion and refactoring on the market; excellent debugger Spring-specific support (bean navigation, application.properties completion) is Ultimate-only
Eclipse / Spring Tool Suite (STS) Yes, entirely free STS is Eclipse with Spring tooling already bundled: bean view, launching Boot applications, assisted property editing Less polished interface; fairly memory-hungry
VS Code + Extension Pack for Java Yes Lightweight, starts fast, same editor for frontend and backend; the "Spring Boot Extension Pack" is very complete Less capable at large refactorings

What to install in each case

IntelliJ IDEA Community: download it from jetbrains.com or install it with the Toolbox App. It recognises Maven projects automatically when you open the folder containing the pom.xml.

Spring Tool Suite: download STS 4 from spring.io/tools. It ships as a self-executing JAR that unpacks the IDE. To import the project: File → Import → Existing Maven Projects.

VS Code: install these two extensions from the Marketplace.

# From the command line, if you have the "code" command available
code --install-extension vscjava.vscode-java-pack
code --install-extension vmware.vscode-boot-dev-pack
  • Extension Pack for Java: compiler, debugger, Maven support, test runner.
  • Spring Boot Extension Pack: completion in application.properties, a Spring Boot Dashboard view to start and stop applications, endpoint navigation.

Recommended settings, whichever IDE you choose

  1. Check that the IDE is using JDK 21, not an older bundled one. In IntelliJ: File → Project Structure → SDK. In VS Code: the java.configuration.runtimes setting.
  2. Enable automatic builds and format-on-save.
  3. Set the encoding to UTF-8 for the whole project. Ribalta's station names carry accents in their original Spanish spelling ("Parque del Río"), and a badly set encoding produces "Parque del Río".

  1. Tools for testing the API

You are going to build a REST API, so you need something to call it with. It is worth knowing several options:

curl

It is available on practically every system and it is the common language of documentation.

# A simple GET request
curl http://localhost:8080/api/v1/stations

# Also show the response headers and the status code
curl -i http://localhost:8080/api/v1/stations

# POST request with a JSON body
curl -X POST http://localhost:8080/api/v1/stations \
  -H "Content-Type: application/json" \
  -d '{"name":"Main Square","capacity":24}'

The options you will use most: -i (include headers), -X (HTTP method), -H (header), -d (body), -s (silent).

HTTPie

More readable syntax and colourised JSON output. Very comfortable for exploring.

# Installation
sudo apt install httpie        # Debian/Ubuntu
brew install httpie            # macOS

# GET
http :8080/api/v1/stations

# POST: key=value pairs become JSON automatically
http POST :8080/api/v1/stations name="Main Square" capacity:=24

An important detail: name="Main Square" produces a string, whereas capacity:=24 (with :=) produces a number. Sending "24" when the API expects an integer is a frequent mistake.

Postman

A graphical application. Its value lies in organising collections of saved requests, using environment variables ({{baseUrl}}), managing authentication tokens and sharing all of it with the team. Very useful from module 5 onwards, once JWT tokens appear.

.http files

This is my recommendation for the course. They are plain text files, versionable in Git, that IntelliJ (natively) and VS Code (with the REST Client extension) run straight from the editor.

### List every station in Ribalta
GET http://localhost:8080/api/v1/stations
Accept: application/json

### Look up one specific station
GET http://localhost:8080/api/v1/stations/1
Accept: application/json

### Register a new station
POST http://localhost:8080/api/v1/stations
Content-Type: application/json

{
  "name": "River Park",
  "address": "Riverside Walk 12",
  "capacity": 18
}

Each block separated by ### is an independent request with its own run button. Create the file api-ciclourbana.http at the root of the project and keep adding every endpoint you build: you will end up with living, executable documentation of the whole API.

  1. Docker: a requirement for the later modules

In modules 1 to 3 you do not need Docker. From module 4 onwards it is worth having, and it is essential in modules 6, 7 and 8:

  • Module 4: bring up a real PostgreSQL without installing it on your machine.
  • Module 6: Testcontainers starts ephemeral databases for the integration tests.
  • Modules 7 and 8: package CicloUrbana as an image and deploy it.

Installation

  • Linux: install Docker Engine following the official guide for your distribution and add your user to the docker group so you do not need sudo.
  • macOS and Windows: install Docker Desktop. On Windows, enable WSL 2 integration, which is where it works best.
# On Linux, after installing
sudo usermod -aG docker $USER
# Log out and back in for the change to take effect

Verification

docker --version
docker compose version

# A real test: pull and run a minimal image
docker run --rm hello-world

A dry run of what you will do in module 4, purely to confirm everything works:

# Start a temporary PostgreSQL for CicloUrbana
docker run --name ciclourbana-db \
  -e POSTGRES_DB=ciclourbana \
  -e POSTGRES_USER=ciclo \
  -e POSTGRES_PASSWORD=secret \
  -p 5432:5432 \
  -d postgres:16

# Check that it is running
docker ps

# Stop and remove it when you are done
docker stop ciclourbana-db && docker rm ciclourbana-db

  1. Final verification checklist

Save this script as check-environment.sh and run it. If every line responds, your environment is ready.

#!/usr/bin/env bash
echo "=== Environment check for CicloUrbana ==="

echo "--- 1. JDK (21 expected) ---"
java -version 2>&1 | head -1
javac -version 2>&1

echo "--- 2. JAVA_HOME ---"
echo "JAVA_HOME=${JAVA_HOME:-NOT SET}"

echo "--- 3. Maven (optional: we will use ./mvnw) ---"
mvn -version 2>/dev/null | head -1 || echo "Maven not installed (fine if you use the wrapper)"

echo "--- 4. HTTP tools ---"
curl --version 2>/dev/null | head -1 || echo "curl NOT available"
http --version 2>/dev/null || echo "HTTPie not installed (optional)"

echo "--- 5. Docker (needed from module 4 onwards) ---"
docker --version 2>/dev/null || echo "Docker not installed (not mandatory yet)"

echo "--- 6. Git ---"
git --version 2>/dev/null || echo "Git NOT available"

echo "=== End of check ==="

Running it:

chmod +x check-environment.sh
./check-environment.sh

A table of what must hold before moving on to lesson 01-03:

Requirement How to check it Mandatory already?
JDK 21 installed java -version shows 21.x Yes
Compiler available javac -version shows 21.x Yes
JAVA_HOME pointing at JDK 21 echo $JAVA_HOME Yes
IDE installed and using JDK 21 Project settings Yes
HTTP client curl --version Yes
Internet connection Maven will download dependencies Yes
Git git --version Recommended
Docker docker run --rm hello-world From module 4

Common Mistakes and Tips

  • Having a JRE installed instead of a JDK. The symptom is unmistakable: java works but javac does not exist, and Maven fails with "No compiler is provided in this environment". Install the package with the -jdk or -devel suffix.
  • JAVA_HOME pointing at another version. java -version says 21 but Maven compiles with 17. Always trust the Java version: line printed by mvn -version, because that is the one the build actually uses.
  • Using mvn instead of ./mvnw. It works, but it introduces an uncontrolled variable: the Maven version on your machine. On a team, this ends in "it builds for me".
  • Forgetting chmod +x mvnw after cloning. A very frequent slip on Linux and macOS.
  • The first build takes forever. That is normal: Maven downloads hundreds of artefacts into ~/.m2/repository. Later builds use that local cache and are fast. Do not cancel the process halfway, because you can leave corrupt files in the cache.
  • Tip — working offline. If the cache ends up inconsistent, delete the offending folder inside ~/.m2/repository and build again. Deleting the whole ~/.m2 works too, but forces everything to be downloaded once more.
  • Tip — UTF-8 encoding. Set it in the IDE and in the system. Ribalta has stations such as "Parque del Río" and you will see broken accents at the first slip.

Exercises

Exercise 1

Prepare your machine and document the result: install JDK 21, check java -version, javac -version and JAVA_HOME, and state which distribution you chose and why.

Exercise 2

Your team maintains a legacy system on Java 17 and wants to start CicloUrbana on Java 21 on the same laptop. Explain how you would solve this with SDKMAN! and write the specific commands, including how to pin the version per project without changing the system-wide one.

Exercise 3

Create an api-ciclourbana.http file with three requests to endpoints that do not exist yet: list stations, fetch the station with id 1 and create the station "North Station" with capacity 30. Also write the curl command equivalent to the third one.

Solutions

Solution 1

On an Ubuntu machine:

sudo apt update
sudo apt install openjdk-21-jdk

java -version
# openjdk version "21.0.5" 2024-10-15 LTS

javac -version
# javac 21.0.5

echo $JAVA_HOME
# (empty) → it needs to be defined

# Find the real path of the JDK
readlink -f $(which javac)
# /usr/lib/jvm/java-21-openjdk-amd64/bin/javac

# Append to ~/.bashrc
echo 'export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64' >> ~/.bashrc
echo 'export PATH="$JAVA_HOME/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

echo $JAVA_HOME
# /usr/lib/jvm/java-21-openjdk-amd64

Justifying the choice: the OpenJDK from the repositories is convenient on Linux because it is updated along with the system. If you need a specific version that is reproducible across machines, Temurin via SDKMAN! is preferable.

Solution 2

# 1. Install SDKMAN! if you do not have it
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"

# 2. Install the two versions the team needs
sdk install java 17.0.13-tem
sdk install java 21.0.5-tem

# 3. Keep 17 as the system default (the legacy project)
sdk default java 17.0.13-tem

So that CicloUrbana uses 21 without changing the global version, it is declared at the root of the project:

# ciclourbana/.sdkmanrc
java=21.0.5-tem
maven=3.9.9

And in every shell session inside the project:

cd ciclourbana
sdk env          # activates the versions from .sdkmanrc
java -version    # 21.0.5
cd ..
java -version    # back to 17.0.13 (the global one)

The .sdkmanrc file is versioned in Git, so every team member gets the same setup when they clone. With sdk env install all the missing versions are installed in one go.

Solution 3

### 1. List every station in the Ribalta network
GET http://localhost:8080/api/v1/stations
Accept: application/json

### 2. Details of the station with id 1
GET http://localhost:8080/api/v1/stations/1
Accept: application/json

### 3. Register the station "North Station"
POST http://localhost:8080/api/v1/stations
Content-Type: application/json
Accept: application/json

{
  "name": "North Station",
  "address": "Station Avenue 3",
  "capacity": 30
}

The curl equivalent of the third request:

curl -i -X POST http://localhost:8080/api/v1/stations \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
        "name": "North Station",
        "address": "Station Avenue 3",
        "capacity": 30
      }'

A note on the headers: Content-Type describes what you are sending; Accept declares what you want to receive. Confusing them is a classic source of 415 and 406 responses, which we will look at in module 3.

Conclusion

You now have your environment ready and, more importantly, verified: JDK 21 with a correct JAVA_HOME, an IDE pointing at that JDK, an HTTP client to exercise the API and Docker ready for when you need it. You have also seen why the course uses Maven and why we will always invoke ./mvnw instead of mvn: reproducibility for you and for the whole team.

In the next lesson, Creating Your First Spring Boot Application, you will generate the ciclourbana project with Spring Initializr, write your first GET /api/v1/stations endpoint returning Ribalta's stations, run it with ./mvnw spring-boot:run and package it into an executable JAR.

Spring Boot Course

Module 1: Introduction to Spring Boot

Module 2: Spring Boot Core Concepts

Module 3: Building RESTful Web Services

Module 4: Data Access with Spring Boot

Module 5: Security in Spring Boot

Module 6: Testing in Spring Boot

Module 7: Advanced Spring Boot Features

Module 8: Deploying Spring Boot Applications

Module 9: Performance and Monitoring

Module 10: Best Practices and Tips

© Copyright 2026. All rights reserved