Compiler, interpreter, runtime
Three words people use interchangeably and should not — with the error each one produces.
Three words people use as if they were the same thing. They are not, they do different jobs at different times, and knowing which one is talking to you is the fastest way to read an error message.
A compiler translates, ahead of time
A compiler reads your source and produces something else — a different file, in a different language, that a machine can deal with more directly. It does this before the program runs, and it does it once.
javac Hello.java produces Hello.class, and the two files are nothing alike. The source began with readable text. Here is how the output begins:
ca fe ba be 00 00 00 34cafebabe is a joke from 1994 that every Java class file still carries: a marker saying "this is a class file". The 34 is hexadecimal for 52, which is the class-file version Java 8 emits. Not text, not for you.
The compiler's other job is refusing. Before it translates anything it checks that what you wrote is legal, and if it is not, nothing is produced:
Broken.java:1: error: illegal start of type
public class Broken { void x( }That is a compile-time error. The program does not exist yet, so it cannot have run. Every error of this kind is one the compiler caught for free, before a user saw it — which is the argument for languages that check a lot, and Java checks a lot.
An interpreter executes, as it goes
An interpreter does not produce a file. It reads instructions and performs them, one at a time, as it goes. There is no separate translation step and no output artifact — the reading is the running.
The trade is the obvious one. A compiler can spend time optimising because it runs once; an interpreter cannot, because it is running now. But an interpreter can start instantly, and it can do things a compiler cannot, because it knows what is actually happening rather than what might.
Java does both, and that is the point
This is where the three words get tangled, and where most explanations of Java go wrong.
Hello.java ──javac──▶ Hello.class ──JVM──▶ running program
compiler interpreter, then compiler againjavac compiles your source to bytecode — not to instructions for your CPU, but for an imaginary machine. Then the JVM reads that bytecode and interprets it. Then, for the parts that run often, it compiles those to real machine code while the program is running.
So "is Java compiled or interpreted?" has a precise answer: both, at different stages. It is compiled ahead of time to bytecode, interpreted at first, and compiled again at run time where it pays. You will meet the third step properly in the Java course; it is the reason a Java service is slow for its first few seconds and fast afterwards.
you write. Hello.java exists on disk and is text — 111 bytes you could read in any editor. Nothing can run it yet; it is a description.
javac checks. The compiler reads it and REFUSES if it is not legal Java. Nothing is produced. This is the only stage that can point at a line of your source, because it is the only stage reading it.
javac emits. Hello.class is written — not text, not for you. It begins ca fe ba be, a marker every class file has carried since 1994.
JVM loads. You run `java Hello`. The JVM looks for a CLASS by that name. If it cannot find one, it fails here — and this failure is about WHERE things are, not about your code.
JVM runs. The bytecode executes. From here on a failure is a stack trace: it compiled, it loaded, and the values were wrong.
The runtime is what is there while it runs
A runtime is the machinery your program needs present in order to work: memory management, the standard library, the mechanism that turns bytecode into action. For Java that is the JVM plus the class library.
It is a thing that must be installed, which is why "it works on my machine" is so often about the runtime rather than the code. The source compiled fine; the machine that has to run it does not have what it needs, or has a different version of it.
Reading an error by its stage
Three failures, three stages, three shapes — and the shape tells you where to look:
| What you see | Stage | What it means |
|---|---|---|
error: illegal start of type | compile | the source is not legal Java; nothing was produced |
Error: Could not find or load main class NoSuchClass | startup | the source was fine; the runtime cannot find what you asked it to run |
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5 | run | it compiled, it loaded, it ran, and your logic was wrong |
That third one is the interesting category, because the compiler could not have helped. x[5] on a two-element array is perfectly legal Java — the size is not known until the program runs. A whole class of bugs lives in that gap, and most of what "writing careful code" means is shrinking it.
Misconceptions
- "Compiled languages are fast, interpreted ones are slow." Java is both and is fast. The distinction stopped predicting performance about twenty-five years ago.
- "The compiler finds my bugs." It finds a category — the ones that are about form. It cannot know that you meant
+and typed-, and it will not know your list is empty. - "Bytecode is machine code." It is instructions for a machine that does not exist in hardware. That indirection is exactly what lets the same
.classfile run on a laptop and a server with different CPUs.