JDK vs JRE — javac Not Found in Production Builds
javac: command not found in production? JRE omits javac.
- JDK = Tools + JRE — for developers writing Java code
- JRE = JVM + standard libraries — for running Java programs
- JVM = bytecode execution engine — platform-specific, language-agnostic
- Install JDK if you compile code; install JRE-only (pre-Java9) if you only run apps
- Compiling with a newer JDK than runtime JVM causes UnsupportedClassVersionError
- Biggest mistake: thinking javac and java live in the same tool — javac comes only with JDK
Think of writing a letter. The JVM is the postal system — it delivers your letter anywhere in the world without you worrying about roads or planes. The JRE is the envelope, stamp, and postal rules included — everything the postal system needs to do its job. The JDK is the complete stationery kit: the envelope, stamp, AND the pen, paper, and ruler you used to write the letter in the first place. You only need the stationery kit if you're writing letters. Everyone else just needs the postal system to receive them.
Every time you install Java, you're faced with a choice: do you download the JDK or the JRE? Most beginners just pick one at random and hope for the best. That guesswork trips you up the moment something breaks — like trying to compile code and getting a 'javac not found' error — because you installed the wrong thing. Understanding the difference isn't just academic trivia; it's the foundation for every Java project you'll ever set up.
Java was built around one revolutionary promise: write your code once, run it literally anywhere — Windows, Mac, Linux, a smart fridge. That promise only holds because of a clever three-layer architecture: the JVM handles the 'run anywhere' magic, the JRE bundles everything needed to support it at runtime, and the JDK gives developers all the tools to build Java programs in the first place. Each layer has a specific job, and they nest inside each other like Russian dolls.
By the end of this article you'll know exactly what each acronym means, how the three components relate to each other, which one to install for your use case, and you'll be able to explain the whole thing confidently in a job interview. No handwaving — we're going to trace a real Java file from source code all the way to running output, so you can see every layer doing its job.
The JVM — The Engine That Runs Your Java Code Anywhere
The Java Virtual Machine (JVM) is a program that runs on your computer and pretends to be a standardised, imaginary computer. Your Java code doesn't run directly on your CPU — it runs on this imaginary machine. That's the secret behind Java's 'write once, run anywhere' guarantee.
Here's why that matters. Windows, macOS, and Linux all have different CPUs and operating system rules. Normally, software compiled for Windows won't run on a Mac. Java sidesteps this by compiling your code into a neutral format called bytecode — instructions for the imaginary JVM machine, not for any real CPU. Every OS has its own version of the JVM, and each one knows how to translate that bytecode into whatever the local OS understands.
Think of bytecode as a universal recipe written in a language every chef (JVM) speaks, even though the actual kitchen equipment (CPU/OS) differs from country to country. The JVM is also responsible for memory management — it automatically cleans up objects your program no longer needs, through a process called garbage collection. You don't free memory manually in Java; the JVM handles it.
Critically, the JVM only understands bytecode. It cannot read your original .java source file. Something has to compile that source file first — and that's where the other two components come in.
The JRE — The Complete Runtime Package Your Program Needs to Run
The Java Runtime Environment (JRE) is the JVM plus a large library of pre-written code that Java programs rely on at runtime. You can think of the JRE as the JVM bundled with its toolbox.
When your Java program says System.out.println(...), it's not magic — System, out, and println are all classes and methods written by the Java team, pre-compiled, and shipped as part of the JRE. This collection of pre-written classes is called the Java Class Library (or the Java API). It contains thousands of ready-made tools: ways to read files, connect to the internet, format dates, sort lists, and much more.
Without the JRE, the JVM would be like a car engine with no wheels, seats, or fuel system — technically impressive but not going anywhere.
Before Java 9, you could install the JRE standalone — perfect for end users who just wanted to run a Java app, not build one. From Java 9 onwards, Oracle merged the JRE into the JDK for distribution purposes. But the conceptual distinction still exists and still matters, especially when you're creating a minimal production deployment using the jlink tool, which lets you bundle only the JVM + the specific library modules your application actually uses, making your deployment tiny and fast.
java. or javax. is part of the Java Class Library shipped with the JRE. You're using the JRE's toolbox every single time you write import java.util.ArrayList — you just never had to download or install it separately because it came bundled with your JDK.The JDK — The Full Developer Toolkit That Contains Everything
The Java Development Kit (JDK) is the complete package. It contains the JRE (which contains the JVM), plus a set of developer tools you need to actually build Java programs. The most important tool is javac — the Java compiler. It's the program that reads your .java source file and outputs a .class bytecode file that the JVM can then execute.
Other tools bundled in the JDK include: javadoc (generates HTML documentation from your code comments), jar (packages your compiled classes into a single distributable archive file), jdb (a command-line debugger), jshell (an interactive console introduced in Java 9, great for experimenting), and jlink (packages a minimal JRE for your specific app).
The relationship is simple nesting: JDK ⊃ JRE ⊃ JVM. The JDK is the outermost layer. It contains everything. If you're writing Java code — which you are, since you're reading this — install the JDK. Full stop.
When you run javac HelloJVM.java in your terminal, you're using a JDK tool. When you then run java HelloJVM, you're using the JVM inside the JRE inside the JDK. Two different tools in the same box, each doing a distinct job in the pipeline from source code to running program.
.class file with a JVM from Java 8, they'll get an UnsupportedClassVersionError. Always know your target runtime version. Use javac --release 11 MyFile.java to compile for a specific older version.How JDK, JRE, and JVM Work Together — Tracing One Java Program End to End
Let's tie it all together by following a single Java file through its complete lifecycle. This is the story you should be able to tell from memory.
You open your editor and write Greeting.java. At this point nothing has happened yet — it's just a text file. You run javac Greeting.java. The javac compiler (a JDK tool) reads your source code, checks it for syntax errors, and if everything's fine, produces Greeting.class. This .class file contains bytecode — compact, platform-neutral instructions that no human CPU understands natively, but every JVM does.
Now you run java Greeting. The java launcher (part of the JRE) starts the JVM and hands it your .class file. The JVM's class loader finds Greeting.class and loads it into memory. The JVM then runs a Just-In-Time (JIT) compiler that translates the bytecode into native machine code for your specific CPU — this happens at runtime, which is why Java 'warms up' over time and gets faster the longer it runs. Finally, your main method executes, the JRE's System.out.println does its job, and you see output in the terminal.
Three layers. One seamless pipeline. Understanding this flow means you can diagnose almost any Java setup problem on your own.
Choosing Between JDK and JRE: A Production Decision Guide
Now that you understand the layers, here's the practical decision framework for every environment:
- Dev machines: Always install JDK. You need javac, jshell, jar, and other tools. The JDK includes the JRE, so you get everything.
- Production servers running your app: Use a minimal JRE (or a custom runtime image built with jlink). This reduces attack surface and image size. Many teams use
eclipse-temurin:17-jre-alpineas their base. - CI/CD build agents: JDK required for compilation. Make sure the version matches your target environment.
- Containerization: Use multi-stage builds. Stage 1: JDK to compile. Stage 2: JRE to run. This slashes image size by 60-80%.
- End users who just run a desktop app: Historically JRE, but nowadays you bundle a runtime with the app (using jlink or packaging tools like jpackage).
Java 9+ removed standalone JRE distributions from Oracle, but the concept persists. Vendors like Adoptium still offer JRE-only builds. Always read the distribution page carefully — if it says 'JDK' it includes everything; if it says 'JRE' you cannot compile.
There's one more nuance: when you run java from a JDK installation, you're using the JVM that lives inside the JRE that lives inside the JDK. But when you run javac, you're using a tool that only exists at the JDK level. This is why java -version succeeds on both JRE and JDK, but javac -version fails on JRE-only.
- JVM: the drill — platform-specific, runs bytecode.
- JRE: drill + bits and bits — JVM + standard libraries.
- JDK: drill + bits + screwdrivers & wrenches — JRE + javac, jar, javadoc, etc.
- You don't need the whole workshop to use the drill once it's assembled.
- jlink lets you create a custom toolbelt with only the bits your project uses.
The 'javac Not Found' Production Panic
- Always distinguish build-time images (need JDK) from runtime images (JRE or JRE-slim).
- Use multi-stage Docker builds: JDK for compilation, JRE for running.
- Verify installed Java components with 'java -version' vs 'javac -version' on every environment.
Key takeaways
import java.* you write pulls from the library the JRE provides.javac --release <version> to control bytecode compatibility. Never assume the runtime JVM matches your build JDK.Common mistakes to avoid
5 patternsInstalling only JRE and then trying to compile code
javac HelloWorld.java gives 'javac' is not recognized (Windows) or command not found: javac (Mac/Linux).Compiling with a newer JDK than the JVM version on the target machine
java.lang.UnsupportedClassVersionError: Unsupported major.minor version 61.0 when deployed to a server.javac --release 11 MyApp.java. This tells the JDK to produce bytecode compatible with Java 11 even if your local JDK is version 17.Confusing the JVM with the Java language itself
.class bytecode format that Java's compiler produces. The JVM runs all of it without knowing or caring which language was used. Java the language and the JVM are separate things that happen to be developed together.Not using multi-stage Docker builds — deploying with JDK in production
Assuming `java` and `javac` are always at the same version
$JAVA_HOME/bin to PATH. Verify both java -version and javac -version report the same version.Interview Questions on This Topic
Can you explain the relationship between the JDK, JRE, and JVM? Which one contains which?
Frequently Asked Questions
That's Java Basics. Mark it forged?
6 min read · try the examples if you haven't