Skip to main content
  1. Posts/

Running Java 25 Locally on Windows — The Full Setup

Java from Scratch - This article is part of a series.
Part 1: This Article

Java 25 is here, and if you’re on Windows you have a few options for getting it running locally. This post walks through the full setup — from downloading the JDK to running your first program in the console.

Option 1: Download the JDK Directly
#

I’d recommend sticking with open-source JDK distributions. Here are the main options:

ProviderURLNotes
Adoptium (Eclipse Temurin)adoptium.netOpen-source, community-driven — my go-to
Azul Zuluazul.com/downloadsOpen-source, well-maintained, good docs
OpenJDKjdk.java.netThe upstream source — no installer, just archives

Oracle’s JDK is free for non-commercial use, but the licensing has changed multiple times over the years and it’s caused enough confusion that I just avoid it. The open-source builds are functionally identical — same codebase, same features, no licensing headaches.

Pick one. For most people, Adoptium (Eclipse Temurin) is the easiest starting point. Download the .msi installer for Windows x64.

Install it
#

Run the installer. The default install path is usually something like:

C:\Program Files\Eclipse Adoptium\jdk-25.0.2.10-hotspot

Set JAVA_HOME
#

Before running commands, decide whether you want a temporary setup (current terminal only) or a permanent setup (recommended).

Option A: Temporary (current terminal only)
#

Use this if you just want to test quickly in one open cmd session:

set JAVA_HOME=C:\Program Files\Eclipse Adoptium\jdk-25.0.2.10-hotspot
set PATH=%JAVA_HOME%\bin;%PATH%

This is not saved. Closing the terminal resets it.

Option B: Permanent (recommended)#

You can do this in the Environment Variables UI:

  1. Open System Properties → Advanced → Environment Variables
  2. Add or update JAVA_HOME
    • Value (whatever was installed or downloaded): C:\Program Files\Eclipse Adoptium\jdk-25.0.2.10-hotspot
  3. Edit Path and add %JAVA_HOME%\bin

Or do it from command line:

  • User-level (no admin):
setx JAVA_HOME "C:\Program Files\Eclipse Adoptium\jdk-25.0.2.10-hotspot"
setx PATH "%PATH%;%JAVA_HOME%\bin"
  • System-level (admin terminal):
setx JAVA_HOME "C:\Program Files\Eclipse Adoptium\jdk-25.0.2.10-hotspot" /M
setx PATH "%PATH%;%JAVA_HOME%\bin" /M

/M means machine-level (system-wide) variables. Without /M, setx writes user-level variables only.

Close and reopen your terminal, then verify:

echo %JAVA_HOME%
where java
java --version

You should see something like:

openjdk 25.0.2 2026-01-20 LTS
OpenJDK Runtime Environment Temurin-25.0.2+10 (build 25.0.2+10-LTS)
OpenJDK 64-Bit Server VM Temurin-25.0.2+10 (build 25.0.2+10-LTS, mixed mode, sharing)

Option 2: Use IntelliJ’s .jdks Directory
#

This is my preferred approach for quickly testing multiple JDK versions while developing in IntelliJ. For a stable default setup on Windows, install one JDK system-wide, set JAVA_HOME, and keep %JAVA_HOME%\bin in PATH. IntelliJ manages downloaded JDKs in a .jdks directory inside your user folder.

How it works
#

IntelliJ stores downloaded JDKs at:

C:\Users\<your-username>\.jdks\

So you might end up with:

C:\Users\tg\.jdks\openjdk-21.0.2\
C:\Users\tg\.jdks\openjdk-25\
C:\Users\tg\.jdks\corretto-17.0.9\

Each one is a fully functional JDK. No installer needed.

Download a JDK through IntelliJ
#

  1. Open IntelliJ IDEA
  2. Go to File → Project Structure (or press Ctrl + Alt + Shift + S)
  3. Under Platform Settings → SDKs, click the + button
  4. Select Download JDK
  5. Pick your vendor (e.g., Oracle OpenJDK, Temurin, Corretto) and version 25 or 26
  6. IntelliJ downloads it to ~/.jdks/ automatically

That’s it. No system-wide install, no PATH changes, no admin rights needed.

Use the .jdks JDK from the command line
#

If you want to use this JDK outside IntelliJ too, just point to it directly:

"C:\Users\tg\.jdks\temurin-21.0.9\bin\java.exe" --version

Or set it as your JAVA_HOME temporarily in that terminal session:

set JAVA_HOME=C:\Users\tg\.jdks\temurin-21.0.9
set PATH=%JAVA_HOME%\bin;%PATH%
java --version

This is great for quickly switching between Java versions without messing with your system config.

Your First Java 25 Program
#

Break time: stretch, sip water, then come back for code.

Meme break placeholder

Create a file called Hello.java:

void main() {
    IO.println("Hello from Java 25!");
    IO.println("Java version: " + System.getProperty("java.version"));
    IO.println("Java home: " + System.getProperty("java.home"));
}

Compile and run it:

javac Hello.java
java Hello

Output:

Hello from Java 25!
Java version: 25
Java home: C:\Users\tg\.jdks\openjdk-25

Even simpler: run without compiling
#

Since Java 11 (JEP 330), you can run single-file programs directly:

java Hello.java

No javac step needed. Java compiles it in memory and runs it.

From Java 22 (JEP 458), Java also supports launching multi-file source-code programs. That means java can run source programs that span multiple .java files, not just one file.

Try Some Java 25 Features
#

Let’s make sure the setup works with a quick set of modern Java examples.

Modern String Formatting with .formatted()
#

Quick context: String Templates are a separate Java feature, with preview rounds in JEP 430 and JEP 459. They feel similar to JavaScript backticks and Python f-strings. Think:

  • JavaScript: `Hello ${name}`
  • Python: f"Hello {name}"
  • Java template style: STR."Hello \{name}"

I do not recommend STR for production yet because preview features can still change, require --enable-preview, and can complicate upgrades between JDK versions.

In this post, we use .formatted() because it is stable and works out of the box.

void main() {
    var name = "Tebogo";
    var year = 2026;
    
    // Modern string formatting with .formatted()
    IO.println("Hello, %s! Welcome to %d.".formatted(name, year));
    IO.println("Next year will be %d".formatted(year + 1));
    IO.println("Pi to 4 decimals: %.4f".formatted(Math.PI));
}

Records (stable since Java 16)
#

Think of records as Java’s built-in data classes (or data carriers).

The “buy 1, get 5 free” version is:

  • define the state once,
  • get constructor, accessors, equals(), hashCode(), and toString() generated for you.

That means less boilerplate and clearer intent when a type is mostly about holding data.

void main() {
    var point = new Point(10, 20);
    IO.println(point);
    IO.println(point.xCoordinate());
    IO.println(point.yCoordinate());

    var person = new Person("Alex", 34);
    IO.println(person);
    IO.println(person.name());
}

record Point(int xCoordinate, int yCoordinate) {}

record Person(String name, int age) {
    Person {
        if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
    }
}

Pattern Matching with switch (stable since Java 21)
#

Pattern matching lets Java unpack values while checking their type, so your code reads in a more functional style instead of nested if + casts.

It gets especially powerful with records because you can match the record type and immediately bind meaningful names (like radius, width, height) right inside the switch cases.

void main() {
    var shapes = new Shape[] {
        new Circle(5),
        new Rectangle(4, 6),
        new Triangle(3, 8)
    };

    for (var shape : shapes) {
        IO.println("%s -> area = %.2f".formatted(shape, area(shape)));
    }
}

sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double base, double height) implements Shape {}

static double area(Shape shape) {
    return switch (shape) {
        case Circle(var radius) -> Math.PI * radius * radius;
        case Rectangle(var width, var height) -> width * height;
        case Triangle(var base, var height) -> 0.5 * base * height;
    };
}

Run it:

java PatternSwitchDemo.java
Circle[radius=5.0] → area = 78.54
Rectangle[width=4.0, height=6.0] → area = 24.00
Triangle[base=3.0, height=8.0] → area = 12.00

Troubleshooting
#

java is not recognized as an internal or external command Your PATH doesn’t include the JDK bin directory. Double-check JAVA_HOME and make sure %JAVA_HOME%\bin is on your PATH. Open a new terminal after setting it.

Wrong Java version showing up You might have an older JDK on your PATH. Run where java to see which one Windows is finding first. The one at the top wins.

where java

If the wrong one is first, either remove the old JDK from your PATH or put the new one earlier in the PATH variable.

IntelliJ is using a different JDK than expected Check File → Project Structure → Project → SDK. Make sure it points to your Java 25 JDK. Also check Settings → Build, Execution, Deployment → Build Tools → Gradle (or Maven) if you’re using a build tool — those have their own JDK settings.

Summary
#

  • Download a JDK from Adoptium, Oracle, Corretto, or Azul
  • For a stable default on Windows, install one JDK and set system JAVA_HOME + PATH
  • Use IntelliJ .jdks when you want to quickly test multiple Java versions during development
  • Verify your active Java with echo %JAVA_HOME%, where java, and java --version
  • Run source directly: single-file since Java 11 (JEP 330), multi-file launch since Java 22 (JEP 458)
  • Test the new features: records, sealed interfaces, pattern matching with switch

Next up in this series: Checked vs Unchecked Exceptions in Java — A Deep Dive.

Java from Scratch - This article is part of a series.
Part 1: This Article

Share this post