Set Up Java Properly: JDK, Environment Variables, and Your First Program

bee2026-10-0825 min read0 views
How JDK, JRE and JVM really relate, installation on all three platforms, what JAVA_HOME and PATH actually do, running multiple JDK versions, and the six traps almost every beginner hits.
1 / 114
Section
0. The 30-second version
2 / 114

The first step in Java is not writing code — it is putting three things in the right places: a translator, an execution engine, and a lookup directory. Your .java file reads fine to humans and not at all to a machine, so a compiler (javac) translates it into bytecode (.class); that bytecode runs on any machine with a JVM, so a virtual machine (java) executes it on the spot; and neither command sits somewhere your terminal can see by default, so two environment variables have to say where they live. That is the whole article: what to install, how the system finds it, and what happens inside one command once it is found.

3 / 114
类比|Analogy

think of the JDK as a university library. JAVA_HOME is the street address of that library registered with the city — Maven, Gradle and IDEA are the delivery people who look up that address to collect books. PATH is "which floor the borrowing desk is on" — when you walk in yourself (typing java), the front desk uses that to point you at the window. Whether you write the address as "the street" or as "the room holding the window" is exactly where beginners crash.

4 / 114
Diagram
Figure · Chapter map: how many things you are actually installing
Figure · Chapter map: how many things you are actually installing
5 / 114

After this article you should be able to answer three questions:

6 / 114
  • Am I installing a JDK or a JRE, and why does nobody install a standalone JRE anymore?
  • Who reads JAVA_HOME and who reads PATH? What error does each mistake produce?
  • When you type java Hello, which five things happen inside the machine, in order?
7 / 114
Section
1. First, what exactly are you installing? JDK, JRE and JVM
8 / 114

Almost every beginner gets confused here: one guide tells you to install the JDK, another mentions the JRE, and your task manager shows something called a JVM. These are not three parallel things — they are nested, like Russian dolls.

9 / 114
Diagram
Figure 1 · The JDK / JRE / JVM stack
Figure 1 · The JDK / JRE / JVM stack
10 / 114
Table
TermStands forIn one sentenceWhat's inside
JVMJava Virtual MachineThe virtual computer that actually executes programsInterpreter, JIT, GC, memory model
JREJava Runtime EnvironmentThe minimum needed to run Java programsJVM + core libraries
JDKJava Development KitThe complete toolkit for developing JavaJRE + javac / jar / javadoc
11 / 114
Note

to merely run someone else's program, a JRE is enough. To write even a single line of Java, you need the JDK. Modern JDK installers already bundle a JRE, so stop hunting for a separate JRE download page.

12 / 114
Section
1.1 Why pick version 17 or 21
13 / 114

Java ships a new release every six months, but not every release deserves production use. Only LTS releases are meant for production — that is an industry rule:

14 / 114
Table
VersionTypeSupport untilRecommendation
Java 8LTS (ancient)2030Legacy maintenance only
Java 11LTS2026Transitional choice
Java 17LTS2029+The minimum for Spring Boot 3
Java 21LTS2031+Best default for new projects; virtual threads are stable
15 / 114
Trap

Spring Boot 3.x hard-requires JDK 17. Open a Boot 3 project with JDK 8 and it throws UnsupportedClassVersionError within the first second — with a very long, very scary message.

16 / 114
Section
2. Installing on each platform
17 / 114
Animation
Animation · From zero to your first running program
Animation · From zero to your first running program
18 / 114
Section
2.1 Windows: prefer a package manager
19 / 114

Downloading from Oracle's site works, but version management gets painful. Use winget or Scoop instead — one command each:

20 / 114
powershell
# Option 1: winget (built into Win10 1809+)winget install EclipseAdoptium.Temurin.21.JDK# Option 2: Scoop (more flexible version switching)scoop bucket add javascoop install temurin21-jdk
21 / 114

After installation you do not need to configure environment variables by hand — package managers register everything. Manual zip installs do require Section 3.

22 / 114
Section
2.2 macOS: one brew command
23 / 114
bash
brew install --cask temurin@21# Point JAVA_HOME at itecho 'export JAVA_HOME=$(/usr/libexec/java_home -v 21)' >> ~/.zshrcsource ~/.zshrc
24 / 114

macOS ships /usr/libexec/java_home, a built-in JDK locator that is smarter than a hard-coded path — it can pick a version when several are installed.

25 / 114
Section
2.3 Linux: distro packages + SDKMAN
26 / 114
Code
Codebash
# Debian / Ubuntusudo apt update && sudo apt install -y openjdk-21-jdk# For freer version switching, use SDKMANcurl -s "https://get.sdkman.io" | bashsdk install java 21.0.2-temsdk use java 17.0.10-tem    # switch to 17 temporarily
Notes

Tip: on a server, java -version may still show a preinstalled older JDK. That means the old version sits earlier in PATH — run which java to see which one actually wins.

27 / 114
Section
3. Environment variables: what JAVA_HOME and PATH really do
28 / 114

This is the step beginners most often copy wrong, and it is worth understanding properly. The two variables have completely different jobs:

29 / 114
Code
Codebash
JAVA_HOME = D:\dev\jdk-21        # tells TOOLS where the JDK livesPATH     += %JAVA_HOME%\bin      # tells the SHELL where to find java
Notes
  • Who reads JAVA_HOME? Tools — Maven, Gradle, Tomcat, IDEA — to locate a JDK.
  • Who reads PATH? The command shell: when you type java, it walks the PATH directories one by one.
30 / 114
Diagram
Figure · Two lookup chains: tools read JAVA_HOME, the shell reads PATH
Figure · Two lookup chains: tools read JAVA_HOME, the shell reads PATH
31 / 114

The two chains never talk to each other, which is why "who complains when this is wrong" is impossible to memorise. Play a round instead: pick a setting on the left, then its actual job on the right — a wrong pick explains itself on the spot.

32 / 114
Match
MatchWho reads it, and who complains when it is wrongMatched 0/6 · Missed 0
Six hard mappings of setting to job. Do not use positions — both columns are shuffled
Pick a card on the left first
33 / 114
类比|Analogy

the JVM looks for a class the way you look for a book at university — you check the main library first. It asks the central stacks (the class library shipped with the JDK); only if the main library says "we don't hold that" does it try the departmental branch (the jars and folders listed in -cp). This "ask upstairs first, search yourself only if they don't have it" rule is called parent delegation, and it guarantees that a String.java you write in your own project can never overwrite the JDK's String.

34 / 114
Trap

setting JAVA_HOME to D:\dev\jdk-21\bin is the all-time classic. Tools append \bin\java themselves, producing bin\bin\java, and Maven fails with "JAVA_HOME is set to an invalid directory".

35 / 114

To verify, run one command — and read the capitalized version number:

36 / 114
Code
Codebash
$ java -versionopenjdk version "21.0.2" 2024-01-16 LTSOpenJDK Runtime Environment Temurin-21.0.2+13 (build 21.0.2+13-LTS)OpenJDK 64-Bit Server VM Temurin-21.0.2+13 (build 21.0.2+13-LTS, mixed mode)
Notes
  • The three lines are: JDK version, runtime environment, VM implementation
  • 64-Bit Server VM means 64-bit server mode — the production standard
  • If it prints 1.8.0_xxx, your PATH still points at Java 8

Attention: both java -version (single dash) and java --version (double dash) work, but the first is legacy style and the second is proper GNU style.

37 / 114
Section
4. Your first program: the full compile-and-run pipeline
38 / 114

Create a folder and type this in any text editor (save it as Hello.java, not the .txt Notepad suggests):

39 / 114
java
public class Hello {    public static void main(String[] args) {        System.out.println("Hello, Java!");        // main is the entry point the JVM expects: a fixed signature        for (int i = 0; i < args.length; i++) {            System.out.println("arg " + (i + 1) + ": " + args[i]);        }    }}
40 / 114

Open a terminal in that folder and run two commands:

41 / 114
Code
Codebash
# Step 1: compile source into bytecodejavac Hello.java        # produces Hello.class# Step 2: run it — the JVM loads and executes the classjava Hello alice bob    # note: no .class suffix
Notes
  • javac Hello.java invokes the compiler and emits Hello.class — platform-independent bytecode
  • java Hello starts a JVM, loads Hello.class, and executes main
  • Command-line arguments land in String[] args and get printed by the loop
42 / 114

Why the .class step? Because this is exactly how Java achieves portability: compile once, and the bytecode runs anywhere a JVM exists — that is what "write once, run anywhere" actually means.

43 / 114
Warning

java Hello.class is wrong. The java command always takes a class name (Hello), never a file name. Four extra characters and the JVM reports it cannot find the class.

44 / 114

Those five stages (find the command → load the JVM → build the classpath → load the main class → call main) read like a rhyme, but failures always land in one specific box. Spread them out as a single-step run: the six lines on the left, and on the right the variables and call stack as they change. Press step repeatedly and watch step 4 — that is where the Section 9 error is born:

45 / 114
Stepper
StepperStep by step: what those seven characters java Hello actually do1 / 6
Six beats. The classpath built in step 4 decides whether step 5 can find anything
Code under debug
1javac Hello.java # the shell walks PATH and finds javac
2java Hello # same rule, this time it finds java.exe
3// java.exe is only a launcher: it pulls jvm.dll / libjvm.so into the process
4// build the classpath: -cp first, then the CLASSPATH variable, then the current dir
5// load Hello.class by its fully qualified name and turn it into a Class object
6// invoke public static void main(String[])
Variables now
command looked upjavac
decided bythe order of PATH
outputHello.class
Call stack
1shell → PATH → javac.exe
1Nothing Java-specific yet: the operating system is hunting for an executable, trying each PATH directory left to right and taking the first hit. With three JDKs installed, whoever is listed first is 'your javac'.
46 / 114
Section
5. Multiple JDKs: don't let legacy projects tie your hands
47 / 114

In real work you may maintain a JDK 8 system in the morning and build a JDK 21 project in the afternoon. Never uninstall and reinstall to switch — use a manager:

48 / 114
Table
ToolPlatformKey commandBest for
SDKMANmacOS / Linuxsdk use java 17.0.10-temCommand-line users
jenvmacOS / Linuxjenv global 21Per-project configs
winget + manual PATHWindowsDrag entries in the env dialogOccasional switching
IDEA's JDK dropdownAllFile → Project StructureSwitching inside the IDE only
49 / 114

The hard part of running several JDKs is not installing them but knowing which one is actually in charge, because three parties each follow their own rule: the shell follows PATH order, tools follow JAVA_HOME, the IDE follows its Project SDK. This animation runs the three lines side by side — pay attention to frame 6, the moment they disagree, which is exactly when "works in the IDE, dies in the terminal" is born:

50 / 114
Animation
Animation · Three JDKs installed — which one actually runs your code
Animation · Three JDKs installed — which one actually runs your code
51 / 114

"Don't let a laptop decide the build" boils down, in practice, to a few lines of pom. Rather than copying someone's, tick the boxes and watch what it emits — especially the java.version line and the three scopes:

52 / 114
Generator
GeneratorPut the JDK version in the project, not on the laptoppom.xml2 / 5
Set the Java version above to 21 and watch it land in <java.version>; then tick MySQL / Lombok / Test and compare the three generated scopes — runtime, provided and test are the stars of article 2, section 4
Output
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.4</version> <!-- 版本由 BOM 统管,子依赖不写 version -->
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>demo-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>21</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
Why each choice matters
parentInheriting 3.3.4 starter-parent means no spring-boot-starter-* needs a version; the moment someone adds an explicit version to one starter, that one wins — the most common source of dependency drift.
WebAnything that serves HTTP needs it: DispatcherServlet, embedded Tomcat and JSON mapping come inside this starter.
Testscope=test: @SpringBootTest, MockMvc and AssertJ live here; without it @Test is unresolved.
53 / 114
Decision
Decisionyour IDE uses JDK 21, but the CI pipeline uses JDK 17. Code that compiles locally fails on CI right after you push. What is the right first move?
54 / 114
Section
6. Six traps to dodge early
55 / 114
Table
SymptomRoot causeFix
'javac' is not recognizedPATH missing or staleReopen the terminal; ensure PATH contains %JAVA_HOME%\bin
JAVA_HOME is set to an invalid directoryJAVA_HOME includes \binPoint it at the JDK root
Error: Could not find or load main class HelloClass and file names differ / ran the .classMake them match; java Hello
UnsupportedClassVersionErrorOld JDK running newer bytecodeUpgrade the JDK to 17+
unmappable character for encodingNon-ASCII source without an explicit charsetjavac -encoding UTF-8 Hello.java
Runs in IDEA, fails in the terminalThe two use different JDKsCompare which java with IDEA settings
56 / 114
Section
7. Hands-on labs: turn the invisible layer on screen
57 / 114

Environment setup is hard to remember because it is path lookup you cannot see. These four kernel experiments really execute the JDK's layers in your browser, and every one lets you flip the parameter yourself.

58 / 114

The first unpacks "the life of one line of code". Bytecode is the intermediate language javac emits; a class loader is the role that reads .class files into memory; JIT (just-in-time) compilation is the accelerator that turns hot bytecode into machine code once the JVM notices a loop running often. Switch to "JIT tiers" and you watch the same loop get interpreted first and compiled later — that is why Java feels slow at startup and fast afterwards:

59 / 114
Kernel lab
TeaVMThe journey of Hello.java: compile phase, loading, interpretation, JITidle
Press the four buttons in order; watch execution speed change under JIT
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
60 / 114

The second answers the analogy from Section 3 head-on: why a String.java of yours can never beat the JDK's String. Pick "parent delegation" to watch a request climb upward; pick "duplicate jars" to see which copy wins when two jars hold the same class — the one listed earlier in the classpath, which is exactly the root cause behind the NoClassDefFoundError row in the Section 9 table:

61 / 114
Kernel lab
TeaVMMain library first: classpath and parent delegationidle
Switch to duplicate jars and see why only one copy of a duplicated class survives
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
62 / 114

Flip the same mechanism around and you get the error that breaks most beginners. On "ClassNotFoundException" you literally watch the JVM walk every directory on the classpath and come back empty-handed:

63 / 114
Kernel lab
TeaVMWhere did it look for the main class: one failed searchidle
Compare with 'Could not find or load main class' in Sections 6 and 9
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
64 / 114

The third explains something you will meet constantly from the next article on: IDEA quietly does one job before Run. Under "compile output" you see .java become .class inside target/classes; run without compiling first and you are executing stale bytecode:

65 / 114
Kernel lab
TeaVMFrom save to run in IDEA: a revised draft must be reprintedidle
Click compile output, then run — reverse them and you understand what 'running the old build' means
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
66 / 114

The fourth experiment answers Section 3's question directly: which JDK is actually working. On "who wins" you watch the shell walk PATH and take the first hit; on "JAVA_HOME vs PATH" the two readers separate; "several JDKs installed" is the lifelike one — 8, 17 and 21 coexisting, with tools, shell and IDE each picking their own:

67 / 114
Kernel lab
TeaVMThree JDKs installed — which one is workingidle
Cycle which → home → multi; the last one lines up 8 / 17 / 21 and shows who wins
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
68 / 114
Kernel lab
TeaVMVerify and repair: how to confirm the fix really tookidle
This mode gives an executable self-check order, which is the practical side of Section 5's 'put the version in the project'
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
69 / 114

The order of that lookup chain is not something you absorb by reading — you have to click. This diagram splits "main library first" into five boxes:

70 / 114
Diagram
FlowThe full route of one class lookup (click through it)1 / 5
Go from ① to ⑤; box ③ is the one people misremember — you only search yourself once upstairs says 'not ours'
→
→
→
→
① A fully qualified name arrives
The Hello in `java Hello`, or any imported type, ends up as a path like com/example/Foo.class. A package name is not decoration — it is the directory structure, which is why article 3 complains when 'package does not match path'.
All clearIn one line: lookup order is priority, and the JDK always sits in front of your jars.
71 / 114

Finally, a preview of how an application dies when the environment is wrong. It is not a one-line 'class not found' but a full screen — the triage desk below trains exactly that reading:

72 / 114
Kernel lab
TeaVMA startup failure scene: how to read that wall of textidle
Hunt for the caused by line, usually at the bottom — the same reading skill used in Section 9 and the triage block
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
73 / 114

Once you are bored of buttons, type the commands yourself. This console talks to the same in-browser Java kernel, and every reply is computed there — start with whoami to see which JDK is live, then work through the lab envpath lines:

74 / 114
Console
75 / 114
Note

run lab envpath home followed by lab envpath fix — the first reproduces the trailing-\bin mistake, the second gives the verification order. Same error: first understand why it happens, then confirm it is really gone.

76 / 114
Section
8. Sandbox: where JAVA_HOME should point, and which version to pick
77 / 114
Animation
Animation · What happens inside the JDK when you type java Hello
Animation · What happens inside the JDK when you type java Hello
78 / 114

Steps 1 and 3 of that chain are both things you can write wrong. Change the address format on the left and the right panel immediately says who reads it and what it throws; then pick a JDK version below to see where Spring Boot and the language features each draw their line:

79 / 114
Sandbox
SandboxJAVA_HOME path + JDK version vs language features
Result
mvn -v: OK
Spring Boot 3.x: OK
switch pattern matching available
The standard setup for this tutorial and most current guides.
80 / 114
Note

the cell beginners overlook most is "jre-only". Oracle stopped shipping a standalone JRE from JDK 11, so any "JRE" you download today is a trimmed JDK — when javac is not recognized, first check whether your copy ships a compiler at all (look for javac inside bin).

81 / 114
Section
9. Common errors, searchable by exact wording
82 / 114

Every phrase in the first column can be pasted straight into a search box — do not paraphrase or shorten it:

83 / 114
Table
Error text (excerpt)Real cause30-second fixDig deeper in
错误: 找不到或无法加载主类 Hello / Error: Could not find or load main class HelloYou passed a file name instead of a class name, the .class is not on the classpath, or the package and folder disagreeUse java Hello (no .class); confirm the bytecode exists with dir target\classesSections 4 & 6 · #3 IDEA project
java.lang.UnsupportedClassVersionError: Hello has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0A JDK 8 launcher trying to run bytecode compiled by JDK 17 (61 = 17, 52 = 8)Align java -version with the Java version printed by mvn -v; move both to 17+Section 3 · #36 packaging
The JAVA_HOME is not defined correctly / JAVA_HOME is set to an invalid directoryJAVA_HOME unset, pointing at bin, or pointing at a JRE-only folderPoint it at the JDK root (e.g. D:\dev\jdk-21) and reopen the terminal, then echo %JAVA_HOME%Section 3 · sandbox above
'mvn' is not recognized as an internal or external command ('mvn' 不是内部或外部命令)Maven's bin is not on PATH, or you edited PATH without reopening the terminalAdd %MAVEN_HOME%\bin to PATH, open a new window, verify with mvn -v#2 Maven basics
'java' is not recognized as an internal or external commandPATH lacks %JAVA_HOME%\binAppend it to PATH; on Windows drag the entry up so it wins over older onesSection 3
错误: 编码 GBK 的不可映射字符 (unmappable character for encoding GBK)UTF-8 source containing Chinese, compiled with the platform default charsetjavac -encoding UTF-8 Hello.java; set UTF-8 globally in IDEA#35 logging · #3 IDEA
Exception in thread "main" java.lang.NoClassDefFoundError: com/example/FooPresent at compile time, absent at runtime — the jar is missing from the runtime classpathjava -cp "target/classes;lib/*" com.example.MainSection 7 classpath labs
84 / 114
Trap

Could not find or load main class fuses two different causes — the class file was not found (a path/classpath problem) and it was found but failed to load (a version mismatch, or an exception thrown in a static initializer). When you cannot tell which, rerun with -verbose:class and the JVM prints where every class came from.

85 / 114

Version mismatch is the most common half of that fused message. Here is a real stack — do not read the answer, just click the frame you think is guilty:

86 / 114
Triage
Error triageUnsupportedClassVersionError: class file version 61.0
Compiled by a newer JDK than the one running it: a full screen with not one business class in it

You build the jar locally on JDK 21 and hand it to a CI box that only has JDK 8. The process never reaches main.

java.lang.UnsupportedClassVersionError: com/bee/ordersvc/OrderServiceApplication has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1017)
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
at java.base/java.net.URLClassLoader.defineClass(URLClassLoader.java:555)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:635)
Click the frame you blame — guessing is allowed
No pressure: guess the exception first, then which line actually made the call.
87 / 114
Section
10. Check yourself
88 / 114
Quiz
Check yourselfYou configured JAVA_HOME and PATH on Windows, then typed java -version in a PowerShell window you had already opened — still 'not recognized'. Most likely reason?
Pick one — you get feedback right away
89 / 114
Quiz
Check yourselfA colleague says: 'My project runs fine on JDK 21, so there is no need to declare a version in pom.xml — everyone has 21 installed.' What is the biggest flaw?
Pick one — you get feedback right away
90 / 114
Section
11. Practice in three levels
91 / 114
Section
Level 1 · Follow along
92 / 114

Goal: walk the whole "compile → run → break it on purpose → read the error" chain in a plain terminal, with no IDE involved.

93 / 114
java
// EnvCheck.java — drop it in any empty folder; the file name MUST be EnvCheck.javapublic class EnvCheck {    public static void main(String[] args) {        System.out.println("java.version = " + System.getProperty("java.version"));        System.out.println("java.home    = " + System.getProperty("java.home"));        System.out.println("user.dir     = " + System.getProperty("user.dir"));        System.out.println("classpath    = " + System.getProperty("java.class.path"));        for (int i = 0; i < args.length; i++) {            System.out.println("arg[" + i + "] = " + args[i]);        }    }}
94 / 114

Run these in order and compare the output:

95 / 114
bash
javac EnvCheck.javajava EnvCheck alice bobjava -cp . EnvCheck          # spell out the classpath, so -cp stops being magicjava EnvCheck.class          # ← break it deliberately and read the error
96 / 114

Expected output (the version line follows your own JDK):

97 / 114
text
java.version = 21.0.2java.home    = D:\dev\jdk-21user.dir     = D:\tmp\envcheckclasspath    = .arg[0] = alicearg[1] = bob
98 / 114

Done when: all four property lines print, and you can say out loud why java never takes a .class suffix.

99 / 114
Section
Level 2 · Variants
100 / 114

Three tiny edits, one variable at a time — note what changes each time:

101 / 114
  1. Temporarily rewrite JAVA_HOME with a trailing \bin and run mvn -v. You will observe Maven failing with The JAVA_HOME is set to an invalid directory while java -version still works — they do not read the same thing.
  2. Point the classpath somewhere wrong, e.g. java -cp nonexistent EnvCheck. You will observe Could not find or load main class; replay the classpath experiment in Section 7 to see exactly which directories were walked.
  3. Compile the same class twice into two folders (javac -d out1 EnvCheck.java, edit one print line, javac -d out2 EnvCheck.java), then run with -cp "out1;out2" and with "out2;out1". You will observe the printed output following the classpath order — that is "the earlier duplicate jar wins".
102 / 114

Tip: after variant 3, go back to the "duplicate jars" mode in Section 7 and the abstract rule suddenly becomes concrete.

103 / 114
Section
Level 3 · Build something
104 / 114

Write your own environment health check script — the thing you will thank yourself for on every new machine.

105 / 114
  • check-env.ps1 on Windows, check-env.sh on macOS / Linux
  • Print at least: the real java -version, javac -version, the Maven and Java lines from mvn -v, the resolved path from where java / which java, and whether JAVA_HOME points at a root rather than bin
  • Each check prints either [OK] or [FAIL] reason + one-line fix, and the script exits 0 only when everything passes
  • Bonus: detect multiple installed JDKs and list them all (/usr/libexec/java_home -V on macOS, update-alternatives --list java on Linux)
106 / 114

Acceptance checklist: ① removing java from PATH produces a FAIL instead of a crash; ② running it on two machines pinpoints "different JDK version" from the diff alone; ③ you, three months from now, still understand those FAIL messages.

107 / 114
Section
12. Self-check
108 / 114
Self-check

state the JDK / JRE / JVM containment relation from memory, and explain why nobody installs a standalone JRE anymore.

109 / 114
Self-check

who reads JAVA_HOME and who reads PATH? If JAVA_HOME ends in ...\bin, which tool complains and with what exact sentence?

110 / 114
Self-check

why can't you write .class after java Hello? What does the JVM think your string is?

111 / 114
Self-check

bytecode is what makes "write once, run anywhere" true — which layer is being made portable, source or machine code?

112 / 114
Self-check

two jars contain the same class name. Which copy loads, and how does that match the "main library first" analogy?

113 / 114
Mnemonic

the JDK supplies, JAVA_HOME locates, PATH hails, bytecode runs — tools to install, paths to find, the command you shout, and the platform-neutral class that actually executes.

114 / 114
Summary

this article boils down to three facts — the JDK contains the JRE which contains the JVM; JAVA_HOME points at the root while PATH points at bin; let tools manage versions instead of manual hacking. Get these right and no later tutorial will ever be interrupted by environment issues again.