“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.
Sir, Kabir's laptop says java -version works, but javac “is not recognized”. If Java is installed, why is the compiler missing?
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.
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!
Exactly. Let's open each box and see what's inside.
What are the JDK, the JRE and the JVM?
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.
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
The three at a glance
| JVM | JRE | JDK | |
|---|---|---|---|
| Full form | Java Virtual Machine | Java Runtime Environment | Java Development Kit |
| Contains | Class loader, memory areas, execution engine | JVM + class libraries | JRE + development tools |
| Job | Runs bytecode | Provides everything to run programs | Provides everything to develop programs |
| Has javac? | No | No | Yes |
| Who needs it | Comes inside the JRE/JDK | Servers and users who only run apps | Developers and build servers |
| Platform dependent? | Yes | Yes | Yes |
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.
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.
Loading2. 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.
Memory3. Execution engine
The interpreter runs bytecode, the JIT compiler turns hot code into machine code, and the garbage collector frees unused objects.
RunningWhat the JRE adds, and what the JDK adds
| Layer | Adds | Examples |
|---|---|---|
| JRE | Core class libraries and config files | java.lang (String, Math, System), java.util (ArrayList, HashMap), java.time, java.net, java.sql |
| JDK | Development tools in the bin folder | javac 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.
FreeOracle 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 licenceOther 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 bytecodeIs 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.
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).
Check what you have
Look at the three lines: the version, the Runtime Environment (the JRE part) and the 64-Bit Server VM (the JVM, HotSpot). Ifjava -version javac -versionopenjdk 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.1javac -versionfails, you have a runtime but not a JDK. This sample is from an Ubuntu build; your exact version numbers will differ.Find the JDK folder
On Windows it's usually under
This folder is whatC:\Program Files\Eclipse Adoptium\orC:\Program Files\Java\. On Linux, try/usr/lib/jvm/. On macOS, run/usr/libexec/java_home.JAVA_HOMEshould point to: the JDK's top folder, not itsbinsubfolder.Open the bin folder: these are the JDK tools
You'll see programs such as
Onlyjava,javac,jar,javadoc,jdb,jshell,javap,jlinkandjcmd.javais needed to run programs. Everything else is for developing. That's the difference between the JDK and the JRE. Interesting fact:javacis itself written in Java and runs on the JVM.Look at lib and jmods: the class libraries
The
When your program useslibfolder holds a file calledmodules, which packs all the standard library classes. Thejmodsfolder holds the same modules in a formjlinkcan use.StringorLocalDate, the JVM's class loader reads those classes from thismodulesfile. This is the “library” half of the JRE.List the modules
The JDK is split into modules.java --list-modulesjava.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 …java.base(String, Object, collections…) is needed by every program. Modules starting withjdk., such asjdk.compiler(javac), are JDK-specific tools.
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's laptop can run Java (
java -versionworks) butjavacis not recognized. He only has a runtime. - He installs JDK 21. Its folder has
bin(tools),lib(libraries) and more. He setsJAVA_HOMEandPATH. javac Main.javanow works, and Main.class appears.java Mainruns his program. It usesSystemandLocalDate, classes he never wrote.- The bank's production server runs a finished app with only a runtime. It has no compiler, and doesn't need one.
- The rule: developers install the JDK, machines that only run apps need a runtime, and the JVM inside does the actual work.
- His old install has only the JRE (JVM + libraries). There's no tools layer, so no
javacexists anywhere on his PATH. - The JDK wraps the JRE and adds the developer tools. JDK = JRE + tools, JRE = JVM + libraries.
javac, a JDK tool, compiles the source. It is a Java program itself, so the JVM is running it.- For
java Main, the class loader loadsMainfrom his folder andString/LocalDatefrom the JRE's libraries. The execution engine runsmain. - The server has only a runtime: the JVM plus the library modules the app needs, often built with
jlink. - The nesting to remember: JVM ⊂ JRE ⊂ JDK.
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.
- Write: Riya types
Bill.javaon her laptop. Her laptop has the full JDK (tools + runtime). - Compile:
javac, a tool that comes only with the JDK, turns Bill.java intoBill.class(bytecode). - Ship: she copies only
Bill.classto the shop's PC. The shop doesn't need the source code. - Run: the shop's PC has a Java runtime (JRE). Its JVM loads the class, checks the bytecode, and runs
main. - Same output: both computers print the same bill, because the same bytecode runs on any JVM.
- But: typing
javacon the shop's PC fails. A runtime can run programs but has no compiler. Developers need the JDK; users only need a runtime.
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.
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.
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.
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.
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.
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.
Examples are simplified to show where each box fits. Each company's exact setup varies.
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());
}
}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.vendoris who built your JDK,java.vm.nameis the JVM.Main.classAn object that describes theMainclass itself. Every loaded class has one.getClassLoader().getName()Your own classes are loaded by the application class loader, namedapp.String.class.getClassLoader()Printsnull! Core classes likeStringare loaded by the bootstrap loader, which is built into the JVM and isn't a Java object, so Java shows it asnull.
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.
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.
today 2026-10-15, exam 2026-12-02
Exam on: 2026-12-02 (WEDNESDAY) Days left: 48
Approach
- Create two
LocalDateobjects withLocalDate.of. ChronoUnit.DAYS.between(a, b)counts the days.- 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.
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.
principal 100000, rate 7%, 3 years
Maturity amount: Rs 122504 Interest earned: Rs 22504
Approach
- Store principal, rate (0.07) and years.
Math.pow(1 + rate, years)raises to a power.Math.roundrounds 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.
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.
" asha VERMA "
[ asha VERMA ] length 15 [ASHA VERMA] length 10
Approach
trim()removes spaces at both ends.toUpperCase()makes every letter capital.- 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.
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
jlinkruntime. - 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.
Square root
Print the square root of 144 using Math.sqrt.
144
12.0
Show solution and explanation
public class Main {
public static void main(String[] args) {
System.out.println(Math.sqrt(144));
}
}How it works: Math is part of the JRE; sqrt returns a double, so it prints 12.0.
Bigger of two numbers
Print the larger of 75 and 120 using Math.max.
75, 120
120
Show solution and explanation
public class Main {
public static void main(String[] args) {
System.out.println(Math.max(75, 120));
}
}How it works: library methods save you from writing common logic yourself.
Upper-case a city
Print "mumbai" in capitals.
mumbai
MUMBAI
Show solution and explanation
public class Main {
public static void main(String[] args) {
System.out.println("mumbai".toUpperCase());
}
}How it works: you can call a method directly on a string literal.
Next week's date
Print the date 7 days after 2026-10-15.
2026-10-15
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));
}
}How it works: plusDays returns a new date; it handles month ends for you.
Round a price
Round Rs 249.67 to the nearest rupee.
249.67
250
Show solution and explanation
public class Main {
public static void main(String[] args) {
System.out.println(Math.round(249.67));
}
}How it works: Math.round rounds .5 and above up and returns a long.
Delivery date
An order placed on 2026-10-28 is delivered in 5 days. Print the delivery date and weekday.
2026-10-28, 5 days
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() + ")");
}
}How it works: the date moves into November correctly without any month logic in your code.
Area of a circular stall
A circular food stall has a radius of 3.5 m. Print its floor area rounded to 2 decimals.
r = 3.5
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");
}
}How it works: Math.PI is a constant in the JRE. Multiplying by 100, rounding, and dividing by 100.0 keeps two decimals.
Masked phone number
Show only the last 4 digits of 9876543210, like XXXXXX3210.
9876543210
XXXXXX3210
Show solution and explanation
public class Main {
public static void main(String[] args) {
String phone = "9876543210";
System.out.println("XXXXXX" + phone.substring(6));
}
}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.
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.
- Find which Java is being used:
where javaon Windows orwhich -a javaon Linux and macOS. - Set
JAVA_HOMEto the JDK 21 top folder, and put%JAVA_HOME%\bin(or$JAVA_HOME/bin) first in PATH. - Open a new terminal and confirm both
java -versionandjavac -versionshow 21, then checkmvn -v, which prints the Java it uses. - 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.
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.
jdepscan list which modules the app needs, andjlinkcan 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.
“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.
- Check whether Java is installed:
java -version. If not, install a supported JDK or JRE version for that Kafka release. - Find the install folder (for example
/usr/lib/jvm/java-21-openjdk-amd64) and setexport JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64. Point it at the top folder, notbin. - Make it permanent in the service's environment file or the user's profile, so it survives a restart.
- Start Kafka again and check the logs.
Avoid: setting JAVA_HOME to …/bin/java. Scripts add /bin/java themselves, so the path breaks.
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.