JDK vs JRE vs JVM

“Install Java” can mean three different things: the JDK, the JRE or the JVM. Mixing them up is why javac sometimes “is not recognized”. In this lesson you'll see exactly what is inside each one, how they nest inside each other, and which one a developer, a server and a user actually need.

Module 1 · Introduction to Java⏱ 16 minBeginnerNo install needed8 practice problems
1 The story

“javac is not recognized”

Riya's cousin Kabir has just joined the course. His laptop already “has Java”: a banking app and a game installed it years ago. He types his first command and gets an error. He calls Riya, and Riya calls Arjun Sir.

Command Prompt C:\code> java -version java version "1.8.0_…" C:\code> javac Main.java 'javac' is not recognized as an internal or external command… Kabir: only a runtime (old JRE) Command Prompt C:\code> javac Main.java C:\code> java Main Namaste from Java! Riya: full JDK 21 myphatshala.in
Riya

Sir, Kabir's laptop says java -version works, but javac “is not recognized”. If Java is installed, why is the compiler missing?

Arjun Sir

Because what's on his laptop is only a runtime: enough to run Java programs, not to build them. Think of a restaurant. The JVM is the cook who follows recipes. The JRE is the cook plus a stocked kitchen. The JDK is that kitchen plus the tools to write and test new recipes.

Riya

So the game and the banking app only needed the cook and the kitchen. Kabir needs the full JDK because he's writing new recipes!

Arjun Sir

Exactly. Let's open each box and see what's inside.

2 Definition

What are the JDK, the JRE and the JVM?

Formal definition (write this in exams)

JVM (Java Virtual Machine) is an abstract machine that loads, verifies and executes Java bytecode and manages memory at run time. JRE (Java Runtime Environment) is the JVM plus the core class libraries and supporting files needed to run Java applications. JDK (Java Development Kit) is the JRE plus development tools such as the compiler (javac), jar, javadoc and the debugger, used to develop Java applications.

In simple words

The JVM runs bytecode. The JRE is the JVM with the ready-made library classes (like String and System) that every program uses. The JDK is everything in the JRE, plus the tools a programmer needs to write, compile and package programs. JDK = JRE + tools, and JRE = JVM + libraries.

Words you'll meet in this lesson

Class libraryThousands of ready-made classes (String, Math, ArrayList, LocalDate…) that ship with Java.
Class loaderThe part of the JVM that finds .class files and loads them into memory.
Execution engineThe part of the JVM that actually runs bytecode: the interpreter, the JIT compiler and the garbage collector.
HotSpotThe most widely used JVM implementation, the one inside OpenJDK and Oracle JDK.
JAVA_HOMEA setting that tells other programs (Maven, Kafka, IDEs) which JDK folder to use.
jlinkA JDK tool (Java 9+) that builds a small custom runtime containing only the modules your app needs.

The three at a glance

JVMJREJDK
Full formJava Virtual MachineJava Runtime EnvironmentJava Development Kit
ContainsClass loader, memory areas, execution engineJVM + class librariesJRE + development tools
JobRuns bytecodeProvides everything to run programsProvides everything to develop programs
Has javac?NoNoYes
Who needs itComes inside the JRE/JDKServers and users who only run appsDevelopers and build servers
Platform dependent?YesYesYes

Exam tip: all three are platform dependent: you download a different JDK for Windows, macOS and Linux. Only the bytecode (.class files) is platform independent. Also remember the nesting: JVM ⊂ JRE ⊂ JDK.

3 Types and ways to use it

Inside each box, and the different versions you'll meet

The three parts of the JVM

1. Class loader subsystem

Finds .class files, loads them, links them (including the bytecode verifier's safety check) and initialises them. Three built-in loaders: bootstrap, platform and application.

Loading

2. Runtime data areas

The JVM's memory: the heap (objects), a stack per thread (method calls), the method area (class information), plus PC registers and native method stacks.

Memory

3. Execution engine

The interpreter runs bytecode, the JIT compiler turns hot code into machine code, and the garbage collector frees unused objects.

Running

What the JRE adds, and what the JDK adds

LayerAddsExamples
JRECore class libraries and config filesjava.lang (String, Math, System), java.util (ArrayList, HashMap), java.time, java.net, java.sql
JDKDevelopment tools in the bin folderjavac compiler, java launcher, jar, javadoc, jdb debugger, jshell, javap, jdeps, jlink, jcmd, jconsole

Different JDK builds and JVM implementations

OpenJDK builds

The open-source Java source code is compiled and packaged by many vendors: Eclipse Temurin, Amazon Corretto, Microsoft Build of OpenJDK, Oracle OpenJDK and others. They behave the same for your code.

Free

Oracle JDK

Oracle's own build of the JDK, with Oracle's licence terms and paid support options. Companies check the licence before using it in production.

Check licence

Other JVMs

HotSpot is the standard JVM. Eclipse OpenJ9 focuses on lower memory use. GraalVM adds an advanced JIT and can compile Java ahead of time into a native program.

Same bytecode

Is there still a separate JRE? Oracle stopped shipping a stand-alone JRE download from Java 11 onwards. Today you either install the full JDK, use a vendor's JRE package (Eclipse Temurin still offers one), or build a small custom runtime with jlink. The idea of a JRE, “JVM + libraries”, is still exactly right.

4 Step by step

Look inside a real JDK on your computer

You don't need to take our word for it. Follow these steps on any computer that has a JDK (or skip to Part 7 to use the live editor).

  1. Check what you have

    java -version
    javac -version
    openjdk version "21.0.12.1" 2026-08-18 OpenJDK Runtime Environment (build 21.0.12.1+1-1-24.04.4-Ubuntu) OpenJDK 64-Bit Server VM (build 21.0.12.1+1-1-24.04.4-Ubuntu, mixed mode, sharing) javac 21.0.12.1
    Look at the three lines: the version, the Runtime Environment (the JRE part) and the 64-Bit Server VM (the JVM, HotSpot). If javac -version fails, you have a runtime but not a JDK. This sample is from an Ubuntu build; your exact version numbers will differ.
  2. Find the JDK folder

    On Windows it's usually under C:\Program Files\Eclipse Adoptium\ or C:\Program Files\Java\. On Linux, try /usr/lib/jvm/. On macOS, run /usr/libexec/java_home.

    This folder is what JAVA_HOME should point to: the JDK's top folder, not its bin subfolder.
  3. Open the bin folder: these are the JDK tools

    You'll see programs such as java, javac, jar, javadoc, jdb, jshell, javap, jlink and jcmd.

    Only java is needed to run programs. Everything else is for developing. That's the difference between the JDK and the JRE. Interesting fact: javac is itself written in Java and runs on the JVM.
  4. Look at lib and jmods: the class libraries

    The lib folder holds a file called modules, which packs all the standard library classes. The jmods folder holds the same modules in a form jlink can use.

    When your program uses String or LocalDate, the JVM's class loader reads those classes from this modules file. This is the “library” half of the JRE.
  5. List the modules

    java --list-modules
    java.base@21.0.12.1 java.compiler@21.0.12.1 java.desktop@21.0.12.1 java.net.http@21.0.12.1 java.sql@21.0.12.1 …
    The JDK is split into modules. java.base (String, Object, collections…) is needed by every program. Modules starting with jdk., such as jdk.compiler (javac), are JDK-specific tools.
5 See it happen

From “not recognized” to a running program: both views

Follow Kabir as he fixes his setup. Left: what he sees. Right: which box (JVM, JRE or JDK) is doing the work.

Kabir gets a real JDK 6 steps
What you see C:\code> java -version java version "1.8.0_…" ✓ works C:\code> javac Main.java 'javac' is not recognized as an internal or external command, operable program… He can run Java programs, but he can't compile his own. He installs JDK 21. The new folder: 📁 jdk-21 📁 binjava javac jar jshell javadoc… 📁 libmodules (all library classes) 📁 jmodsmodules for jlink 📁 confsettings 📄 releaseversion info He also sets JAVA_HOME and adds its bin to PATH. C:\code> javac Main.java C:\code> Folder C:\code: Main.java Main.class ✨ No message means it compiled successfully. C:\code> java Main Namaste from Java! Today is THURSDAY His program used System and LocalDate. He never wrote those classes. They came with Java. Where from? Meanwhile, on the bank's server… $ java -jar payments.jar Started PaymentService in 2.1 s $ javaccommand not found The server only runs a finished app. No compiler is installed, on purpose: smaller, simpler and with fewer tools to attack. Developer laptop → JDK write, compile, test, package Server / user → runtime (JRE) just run the finished program Inside both → the JVM actually executes the bytecode myphatshala.in
Behind the screen JDK javac · jar · jshell · javadoc · jdb · javap · jlink JRE class libraries: String, System, LocalDate, ArrayList… JVM Classloader Memoryheap · stacks Executionengine No JDK layer = no javac anywhere Windows searched every PATH folder and gave up JDK = JRE + tools JRE = JVM + libraries javac (a JDK tool, itself written in Java) checks Main.java and writes Main.class. The JVM below runs javac itself. Class loader loads Main from your folder, and String, LocalDate from the JRE libraries. The execution engine then runs main(). Production server: runtime only Runtime only the modules this app needs (jlink) JVM loads payments.jar and runs it No javac, no debugger: a smaller image and fewer tools available to an attacker. JDK+ javac, jar, jshell… JRE+ class libraries JVM class loader + memory + execution engine runs bytecode myphatshala.in
6 Real-world use

Who installs what in a real company

In a software team, different machines need different boxes. Here's how it usually looks.

Watch it with an example: Riya's chai-bill app goes to the shop

Riya builds the app on her laptop (JDK) and the chai shop only runs it (runtime only). See which part does which job.

Build on a JDK, run on a runtime 6 steps
Animated example Riya's laptop: JDK Shop's PC: JRE only Bill.java javac Bill.class Bill.class JVM: load, verify, run main(...) Total: Rs 75 Total: Rs 75 javac: not found myphatshala.in

1. Developer laptops: full JDK

Every developer installs a JDK, usually the same LTS version as production. IDEs like IntelliJ IDEA and VS Code use it to compile, run tests and debug.

Write code→JDK: javac + JVM→Run and debug

2. Build (CI) servers: JDK

When code is pushed to Git, a build server (GitHub Actions, Jenkins, GitLab CI) compiles it, runs the tests and packages a .jar. It needs the compiler, so it uses a JDK.

git push→CI: JDK compiles + tests→app.jar

3. Production containers: runtime only

The finished app runs in a container (Docker) built on a small runtime image, such as a JRE image or a custom jlink runtime. Smaller images start faster and have fewer tools an attacker could misuse.

app.jar→Runtime-only image→Live service

4. Big data tools: need JAVA_HOME

Apache Kafka, Hadoop and Spark run on the JVM. Before starting them, you install a JDK or JRE and set JAVA_HOME. Their start-up scripts use it to find the right java.

JAVA_HOME→kafka-server-start.sh finds java→Broker running
ART

5. Android phones: no JVM at all

Android apps are written in Java or Kotlin and compiled with a JDK on the developer's machine, but the phone doesn't run a JVM. Build tools convert the bytecode into Android's own DEX format, which the Android Runtime (ART) runs.

Java code→javac → bytecode→DEX→ART on phone
HotSpotOpenJ9

6. Choosing a JVM for memory or speed

Most teams use HotSpot. Some, running many small services, choose Eclipse OpenJ9 for lower memory use, or GraalVM native images for very fast start-up. The bytecode stays the same; only the JVM changes.

Same app.jar→HotSpot / OpenJ9 / GraalVM→Different speed and memory

Examples are simplified to show where each box fits. Each company's exact setup varies.

7 Run the code

Ask the JVM about itself

Java can tell you which runtime and JVM it's running on, and which class loader loaded each class. The first three lines depend on the machine; the last two are the same everywhere.

public class Main {
    public static void main(String[] args) {
        // Which runtime and JVM is running this program?
        System.out.println("Java version: " + System.getProperty("java.version"));
        System.out.println("Vendor:       " + System.getProperty("java.vendor"));
        System.out.println("JVM:          " + System.getProperty("java.vm.name"));

        // Who loaded which class?
        System.out.println("Main loaded by:   " + Main.class.getClassLoader().getName());
        System.out.println("String loaded by: " + String.class.getClassLoader());
    }
}
Java version: 21.0.12 Vendor: Eclipse Adoptium JVM: OpenJDK 64-Bit Server VM Main loaded by: app String loaded by: null

Sample output from Eclipse Temurin 21. Your version and vendor will be whatever is installed.

  • System.getProperty("java.version")Asks the runtime for a built-in setting. java.vendor is who built your JDK, java.vm.name is the JVM.
  • Main.classAn object that describes the Main class itself. Every loaded class has one.
  • getClassLoader().getName()Your own classes are loaded by the application class loader, named app.
  • String.class.getClassLoader()Prints null! Core classes like String are loaded by the bootstrap loader, which is built into the JVM and isn't a Java object, so Java shows it as null.
8 Line by line

Which box does what, at every line

Step through this program and watch which part of the JDK, JRE or JVM is working, along with the stack, heap and console.

Worked examples: real problems, solved and traced in memory

The JRE ships thousands of ready-made classes (the Java class library). These three programs use some of them for everyday business jobs. Watch the stack: when your code calls a library method, the JVM runs code that came with the JRE, not code you wrote.

Example 1Education · exams

Days left until the exam

Problem statement

A coaching app shows how many days are left until a student's exam. Use the JRE's java.time classes to count the days between two dates.

Input
today 2026-10-15, exam 2026-12-02
Expected output
Exam on: 2026-12-02 (WEDNESDAY)
Days left: 48

Approach

  1. Create two LocalDate objects with LocalDate.of.
  2. ChronoUnit.DAYS.between(a, b) counts the days.
  3. Print the date's day of the week too.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 2Banking · deposits

Fixed deposit maturity with Math.pow

Problem statement

A bank's fixed deposit grows with compound interest: amount = principal × (1 + rate)years. Compute the maturity of Rs 1,00,000 at 7% for 3 years, using Math.pow from the JRE, and round to rupees.

Input
principal 100000, rate 7%, 3 years
Expected output
Maturity amount: Rs 122504
Interest earned: Rs 22504

Approach

  1. Store principal, rate (0.07) and years.
  2. Math.pow(1 + rate, years) raises to a power.
  3. Math.round rounds to the nearest rupee.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 3CRM · data cleanup

Clean up a customer name

Problem statement

Names typed at a shop counter often have extra spaces and mixed case. Clean " asha VERMA " into "ASHA VERMA" and print its length before and after, using JRE String methods.

Input
"   asha VERMA  "
Expected output
[   asha VERMA  ] length 15
[ASHA VERMA] length 10

Approach

  1. trim() removes spaces at both ends.
  2. toUpperCase() makes every letter capital.
  3. Each method returns a new String; the original stays unchanged.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

9 Quiz and revision

Check yourself

Quick revision

Lesson 1.3 in one screen

  • JVM: runs bytecode. Parts: class loader subsystem, runtime data areas (heap, stacks, method area…), execution engine (interpreter, JIT, GC).
  • JRE = JVM + class libraries (java.lang, java.util, java.time…). Enough to run programs.
  • JDK = JRE + tools (javac, jar, javadoc, jdb, jshell, javap, jlink). Needed to develop.
  • Nesting: JVM ⊂ JRE ⊂ JDK. All three are platform dependent; bytecode is not.
  • Class loaders: bootstrap (core, shows as null), platform, application (“app”, your classes).
  • Since Java 11, Oracle ships no separate JRE; use a JDK, a vendor JRE, or a jlink runtime.
  • Developers and CI use a JDK; production usually uses a runtime-only image.

Interview and viva questions

What is the difference between JDK, JRE and JVM?

The JVM executes bytecode. The JRE is the JVM plus the class libraries, enough to run Java programs. The JDK is the JRE plus development tools such as javac, jar and the debugger, needed to write and compile programs. JVM ⊂ JRE ⊂ JDK.

Is the JVM a real machine?

No. It's a virtual machine: a program that behaves like a computer with its own instruction set (bytecode), memory areas and execution rules. The JVM specification describes how it must behave, and products such as HotSpot and OpenJ9 are implementations of it.

Can we run a Java program with only the JVM?

Not in practice. Every program uses library classes such as String and System, which come from the class libraries. So you need at least a runtime (JVM + libraries), which is what a JRE is.

What are the main memory areas of the JVM?

The heap (all objects, shared by threads), the stack (one per thread, holding a frame per method call), the method area (class information; in HotSpot this lives in Metaspace), the PC register (current instruction of each thread) and the native method stack. Module 8 covers them in depth.

Why does String.class.getClassLoader() return null?

Because String is loaded by the bootstrap class loader, which is part of the JVM itself and written in native code, not a Java object. Java represents it as null.

Practice problems

Use ready-made classes from the JRE (Math, String, LocalDate) for small tasks. Every output is fixed. The last three are business tasks.

P1Easy

Square root

Print the square root of 144 using Math.sqrt.

Input
144
Expected output
12.0
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        System.out.println(Math.sqrt(144));
    }
}
12.0

How it works: Math is part of the JRE; sqrt returns a double, so it prints 12.0.

P2Easy

Bigger of two numbers

Print the larger of 75 and 120 using Math.max.

Input
75, 120
Expected output
120
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        System.out.println(Math.max(75, 120));
    }
}
120

How it works: library methods save you from writing common logic yourself.

P3Easy

Upper-case a city

Print "mumbai" in capitals.

Input
mumbai
Expected output
MUMBAI
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        System.out.println("mumbai".toUpperCase());
    }
}
MUMBAI

How it works: you can call a method directly on a string literal.

P4Medium

Next week's date

Print the date 7 days after 2026-10-15.

Input
2026-10-15
Expected output
2026-10-22
Show solution and explanation
import java.time.LocalDate;

public class Main {
    public static void main(String[] args) {
        LocalDate d = LocalDate.of(2026, 10, 15);
        System.out.println(d.plusDays(7));
    }
}
2026-10-22

How it works: plusDays returns a new date; it handles month ends for you.

P5Medium

Round a price

Round Rs 249.67 to the nearest rupee.

Input
249.67
Expected output
250
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        System.out.println(Math.round(249.67));
    }
}
250

How it works: Math.round rounds .5 and above up and returns a long.

P6Business problem

Delivery date

An order placed on 2026-10-28 is delivered in 5 days. Print the delivery date and weekday.

Input
2026-10-28, 5 days
Expected output
Delivery: 2026-11-02 (MONDAY)
Show solution and explanation
import java.time.LocalDate;

public class Main {
    public static void main(String[] args) {
        LocalDate delivery = LocalDate.of(2026, 10, 28).plusDays(5);
        System.out.println("Delivery: " + delivery + " (" + delivery.getDayOfWeek() + ")");
    }
}
Delivery: 2026-11-02 (MONDAY)

How it works: the date moves into November correctly without any month logic in your code.

P7Business problem

Area of a circular stall

A circular food stall has a radius of 3.5 m. Print its floor area rounded to 2 decimals.

Input
r = 3.5
Expected output
Area: 38.48 sq m
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        double area = Math.PI * 3.5 * 3.5;
        System.out.println("Area: " + Math.round(area * 100) / 100.0 + " sq m");
    }
}
Area: 38.48 sq m

How it works: Math.PI is a constant in the JRE. Multiplying by 100, rounding, and dividing by 100.0 keeps two decimals.

P8Business problem

Masked phone number

Show only the last 4 digits of 9876543210, like XXXXXX3210.

Input
9876543210
Expected output
XXXXXX3210
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        String phone = "9876543210";
        System.out.println("XXXXXX" + phone.substring(6));
    }
}
XXXXXX3210

How it works: substring(6) returns the characters from index 6 to the end. Apps mask numbers like this on receipts and screens.

Real-world scenario questions (asked in company interviews)

These come straight from day-to-day work with JDKs and runtimes. Decide your answer first, then open the model answer.

Any company · onboardingFresher

New joiner: “java works but the build fails”

On your first day, you install JDK 21 and clone the team's project. java -version prints 17, the Maven build fails with “release version 21 not supported”, and IntelliJ seems fine. Your buddy asks you to sort it out before stand-up.

The interviewer is checking: do you understand that a machine can have several Java installs, and how PATH and JAVA_HOME choose between them?

Show a strong answer

The laptop already had an older Java 17 that comes first on the PATH, so the command line and Maven pick it up. IntelliJ works because it's configured with JDK 21 separately.

  1. Find which Java is being used: where java on Windows or which -a java on Linux and macOS.
  2. Set JAVA_HOME to the JDK 21 top folder, and put %JAVA_HOME%\bin (or $JAVA_HOME/bin) first in PATH.
  3. Open a new terminal and confirm both java -version and javac -version show 21, then check mvn -v, which prints the Java it uses.
  4. Optionally remove the old runtime if nothing else needs it.

Avoid: reinstalling JDK 21 again and again. The install was fine; the PATH order was the problem.

Product company · DevOps1–3 years

The Docker image is too big

Your service's Docker image is built FROM eclipse-temurin:21-jdk, copies the source code in, and compiles it inside the image. The security team flags the image as large and full of unused tools. How would you improve it?

The interviewer is checking: do you know that production needs a runtime, not a full JDK, and how to separate building from running?

Show a strong answer

Use a multi-stage build. The first stage uses the JDK image to compile and package the jar. The second stage starts from a runtime-only image, such as eclipse-temurin:21-jre or a custom jlink runtime, and copies in just the jar.

  • The final image has no compiler, debugger or source code: it's smaller, faster to pull, and gives an attacker fewer tools.
  • jdeps can list which modules the app needs, and jlink can build a runtime with only those modules.
  • Run the container as a non-root user too.

Avoid saying: “The JDK includes the JRE, so it's fine to ship the JDK.” It works, but it isn't good practice in production.

Data engineering · Kafka setupFresher

“JAVA_HOME is not set” while starting Kafka

You're setting up Apache Kafka on a Linux VM for a data pipeline. The start script fails with an error saying it can't find Java or that JAVA_HOME is not set. Your lead asks what's wrong.

The interviewer is checking: do you know that big data tools run on the JVM, and how they find Java?

Show a strong answer

Kafka is a JVM application, so its scripts need to find a java program, through JAVA_HOME or the PATH.

  1. Check whether Java is installed: java -version. If not, install a supported JDK or JRE version for that Kafka release.
  2. Find the install folder (for example /usr/lib/jvm/java-21-openjdk-amd64) and set export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64. Point it at the top folder, not bin.
  3. Make it permanent in the service's environment file or the user's profile, so it survives a restart.
  4. Start Kafka again and check the logs.

Avoid: setting JAVA_HOME to …/bin/java. Scripts add /bin/java themselves, so the path breaks.

Fintech · cloud deployment1–3 years

The container keeps getting killed

A Java service runs in a Kubernetes container with a 1 GB memory limit. Someone set -Xmx2g “for safety”. Under load, the container is killed with an out-of-memory status, but no Java OutOfMemoryError appears in the logs. What's happening?

The interviewer is checking: do you know which part manages memory (the JVM), and how it fits inside the container's limit?

Show a strong answer

The JVM was told its heap may grow to 2 GB, but the container only allows 1 GB in total. When the JVM's memory use crosses 1 GB, the operating system kills the whole process before the JVM itself runs out, so Java never throws OutOfMemoryError.

  • Remove -Xmx2g. Modern JVMs are container-aware and size the heap from the container limit (by default, a quarter of it).
  • Or set -XX:MaxRAMPercentage (for example 70–75) so the heap leaves room for the JVM's other memory: thread stacks, Metaspace and native buffers.
  • Then monitor real memory use and raise the container limit if the app genuinely needs more.

Avoid: making the heap bigger than the container. Total JVM memory is always more than just the heap.