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
- JDK 21: installation and verification
- Managing several Java versions with SDKMAN!
- Maven versus Gradle, and why this course uses Maven
- The Maven wrapper (
mvnw) - Choosing an IDE: IntelliJ IDEA, Eclipse/STS and VS Code
- Tools for testing the API
- Docker: a requirement for the later modules
- Final verification checklist
- Common Mistakes and Tips
- Exercises
- 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 -versionOn Fedora or Red Hat derivatives:
macOS
With Homebrew:
brew install --cask temurin@21
# Check
java -version
# List every JDK installed on the system
/usr/libexec/java_home -VWindows
Download the Eclipse Temurin 21 .msi installer from adoptium.net and, during installation, tick the "Set JAVA_HOME variable" option. Afterwards, in PowerShell:
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 -versionmust exist and match. Ifjavaresponds butjavacsays "command not found", you have installed a JRE or the-devel/-jdkpackage 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"
- 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 javaA very handy detail: SDKMAN! recognises a .sdkmanrc file at the root of the project.
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:
- 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:
- The
pom.xmlis declarative and explicit: it reads top to bottom and hides no logic. While learning, that matters more than speed. - The vast majority of Spring documentation, and of the answers you will come across, uses Maven.
- 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
Expected output:
Apache Maven 3.9.9
Maven home: /home/user/.sdkman/candidates/maven/current
Java version: 21.0.5, vendor: Eclipse AdoptiumLook 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.
- The Maven wrapper (
mvnw)
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 packageThe 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.zipThroughout 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.
- 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
- 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.runtimessetting. - Enable automatic builds and format-on-save.
- 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".
- 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:=24An 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.
- 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
dockergroup so you do not needsudo. - 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 effectVerification
docker --version
docker compose version
# A real test: pull and run a minimal image
docker run --rm hello-worldA 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
- 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:
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:
javaworks butjavacdoes not exist, and Maven fails with "No compiler is provided in this environment". Install the package with the-jdkor-develsuffix. JAVA_HOMEpointing at another version.java -versionsays 21 but Maven compiles with 17. Always trust theJava version:line printed bymvn -version, because that is the one the build actually uses.- Using
mvninstead 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 mvnwafter 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/repositoryand build again. Deleting the whole~/.m2works 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-amd64Justifying 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-temSo that CicloUrbana uses 21 without changing the global version, it is declared at the root of the project:
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
- What Is Spring Boot?
- Setting Up Your Development Environment
- Building Your First Spring Boot Application
- Understanding the Project Structure
- Application Startup and Lifecycle
Module 2: Spring Boot Core Concepts
- Spring Boot Annotations
- Dependency Injection in Spring Boot
- Bean Scope and Lifecycle
- Spring Boot Configuration
- Spring Boot Properties
- Auto-Configuration and Starters from the Inside
Module 3: Building RESTful Web Services
- Introduction to RESTful Web Services
- Creating REST Controllers
- Handling HTTP Methods
- Validating Input Data
- DTOs and Mapping Between Layers
- Exception Handling in REST
- Documenting the API with OpenAPI
Module 4: Data Access with Spring Boot
- Introduction to Spring Data JPA
- Configuring Data Sources
- Creating JPA Entities
- Relationships Between Entities
- Using Spring Data Repositories
- Query Methods in Spring Data JPA
- Transactions and Persistence Management
- Schema Migrations with Flyway
Module 5: Security in Spring Boot
- Introduction to Spring Security
- Configuring Spring Security
- User Authentication and Authorization
- Implementing JWT Authentication
- Method-Level Security and API Hardening
Module 6: Testing in Spring Boot
- Introduction to Testing
- Unit Testing with JUnit
- Mocking with Mockito
- Integration Testing
- Testing with Testcontainers
Module 7: Advanced Spring Boot Features
- Spring Boot Actuator
- Spring Boot Profiles
- Scheduled Tasks and Asynchronous Execution
- Spring Boot with Docker
- Spring Boot and Microservices
- Service Communication and Fault Tolerance
Module 8: Deploying Spring Boot Applications
- Introduction to Deployment
- Deploying to Heroku
- Deploying to AWS
- Deploying to Kubernetes
- Continuous Integration and Delivery
Module 9: Performance and Monitoring
- Performance Tuning
- Caching with Spring Cache
- Monitoring with Spring Boot Actuator
- Using Prometheus and Grafana
- Logging and Log Management
- Distributed Tracing
