Bytecode and Platform Independence

Naveen AnamaneniUpdated · 7 min read

A movie released across India is often dubbed into many languages, one version for each. Subtitles work differently: there's one film, and each viewer reads it in a way they understand. Java chose the subtitle approach. You compile once into bytecode, a single format for every computer, and the JVM on each machine handles the rest.

What Bytecode Is

Bytecode is the instruction set of the Java Virtual Machine. Each instruction is one byte long (that's where the name comes from), sometimes followed by a few bytes of data. There are about 200 of them, and they are simple: push a number, add two values, call a method, return.

Bytecode is:

  • Not machine code. No real processor runs it directly.
  • Not source code. You can't read it the way you read Java.
  • The same everywhere. A .class file compiled on Windows is byte-for-byte identical to one compiled from the same source on a Mac.

Look at It with javap

Take this small method that works out GST:

Gst.java▶ Try it
public class Gst {
    static int gst(int price) {
        return price * 18 / 100;
    }
 
    public static void main(String[] args) {
        System.out.println("GST on Rs. 500: Rs. " + gst(500));
    }
}
Output
GST on Rs. 500: Rs. 90

Compile it, then use javap -c from the JDK to print the bytecode:

Terminal
javac Gst.java
javap -c Gst

The part for the gst method looks like this:

javap output
static int gst(int);
  Code:
     0: iload_0
     1: bipush        18
     3: imul
     4: bipush        100
     6: idiv
     7: ireturn
The operand stack while gst(500) runs

How to read it

  1. iload_0 puts the first parameter, price, on the JVM's operand stack.
  2. bipush 18 pushes the number 18.
  3. imul takes the top two values, multiplies them, and pushes the result.
  4. bipush 100 and idiv divide that result by 100.
  5. ireturn returns the int on top of the stack.

The numbers on the left (0, 1, 3, 4, 6, 7) are byte positions in the method's code. bipush takes two bytes, so the next instruction starts two bytes later. The i at the start of many instructions means int.

Try it yourself: change gst to use price * 0.18 and return a double. Compile, run javap -c Gst again, and spot the instructions that start with d instead of i.

What's Inside a .class File

A .class file isn't just bytecode. It also stores everything the JVM needs to know about the class:

PartWhat it holds
Magic numberThe first four bytes are always CA FE BA BE, so the JVM can recognize a class file
VersionThe class file version, which says which Java version compiled it
Constant poolEvery name, string and number the class uses, such as "GST on Rs. 500: Rs. " and java/io/PrintStream
Class informationThe class name, its parent class and its interfaces
Fields and methodsTheir names, types and modifiers, plus the bytecode of each method

Run javap -v Gst to see all of it, including the constant pool.

Class File Versions

Every Java release has its own class file version number:

Java versionClass file version
Java 852
Java 1155
Java 1761
Java 2165
Java 2569

A JVM can run class files of its own version or older, but not newer ones. Compile with Java 25 and run on a Java 17 JVM, and you get this real error:

Terminal output
Error: LinkageError occurred while loading main class Gst
    java.lang.UnsupportedClassVersionError: Gst has been compiled by a more recent version of the Java Runtime (class file version 69.0), this version of the Java Runtime only recognizes class file versions up to 61.0

Read it as: "compiled by Java 25 (69), but this is Java 17 (61)".

The Same Numbers Everywhere

Part of platform independence is that Java's basic types have fixed sizes on every computer. In C, an int can be 16 or 32 bits depending on the machine; in Java it's always 32.

Sizes.java▶ Try it
public class Sizes {
    public static void main(String[] args) {
        System.out.println("int:    " + Integer.SIZE + " bits");
        System.out.println("long:   " + Long.SIZE + " bits");
        System.out.println("char:   " + Character.SIZE + " bits");
        System.out.println("double: " + Double.SIZE + " bits");
        System.out.println("Max int: " + Integer.MAX_VALUE);
    }
}
Output
int:    32 bits
long:   64 bits
char:   16 bits
double: 64 bits
Max int: 2147483647

This output is identical on Windows, macOS, Linux, a server or a Raspberry Pi. The lesson "Data Types in Java" covers each type in detail.

Where Platform Independence Stops

Bytecode is portable, but your program can still depend on the operating system if you write it that way:

OS-dependent thingPortable way
File paths like C:\data\bill.txtPath.of("data", "bill.txt")
Line endings (\r\n on Windows, \n elsewhere)System.lineSeparator(), or println
Running OS commands such as dir or lsUse Java's file APIs instead
Native libraries (.dll, .so, .dylib)Ship one per platform, or avoid them

Common Mistakes

Thinking bytecode is slow because it's "interpreted". The JVM interprets it only at first; the JIT compiler turns busy code into machine code. The next lesson explains how.

Running newer class files on an older JVM. Check both versions: javac -version where you build and java -version where you run.

Thinking javap shows your source code. It shows the bytecode the compiler produced. Tools called decompilers try to rebuild source from bytecode, but javap doesn't.

Interview Corner

What is bytecode?

Bytecode is the instruction set of the JVM, produced by javac and stored in .class files. It isn't machine code for any real processor; the JVM interprets it and its JIT compiler turns frequently run parts into machine code.

How does Java achieve platform independence?

javac compiles source code into bytecode instead of machine code for one processor. Every platform has its own JVM that runs this same bytecode, and Java's primitive types have fixed sizes everywhere, so one .class file behaves the same on every platform.

What causes UnsupportedClassVersionError?

Running a class file on a JVM older than the compiler that produced it, for example a class compiled by Java 25 (version 69) on Java 17 (which supports up to 61). Fix it by running a newer JVM or compiling with --release for the older one.

Practice

  1. Easy: Compile Gst.java and run javap -v Gst. Find the line that says major version. What number is it, and which Java version does it mean?
  2. Harder: Write a method static int discount(int price) that returns price - price / 10. Predict the bytecode instructions before you look at javap -c.

Quick check: Bytecode

4 questions
1Which tool shows the bytecode inside a class file?
2What are the first four bytes of every class file?
3Class file version 69 was produced by which Java version?
4Which of these can still make a Java program depend on the operating system?

Key Takeaways

Was this tutorial helpful?

Lesson 25 of 482