Compiler, interpreter, runtime

Three words people use interchangeably and should not — with the error each one produces.

5 min read🧭 Programming Foundations

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:

the first 8 bytes of Hello.classplaintext
ca fe ba be 00 00 00 34

cafebabe 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:

plaintext
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.

plaintext
Hello.java  ──javac──▶  Hello.class  ──JVM──▶  running program
             compiler                  interpreter, then compiler again

javac 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 writejavac checksjavac emitsJVM loadsJVM runs
on diskHello.javacan run?noerrors reach you asnothing yet

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.

1 / 5

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 seeStageWhat it means
error: illegal start of typecompilethe source is not legal Java; nothing was produced
Error: Could not find or load main class NoSuchClassstartupthe source was fine; the runtime cannot find what you asked it to run
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5runit 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 .class file run on a laptop and a server with different CPUs.
Progress is saved on this device and to your account when signed in.