Javap Tool in Java
javap is the class file disassembler that comes with the JDK. You give it a compiled class, and it prints what is inside the .class file in readable form: the fields and methods, their exact types, the constant pool, and the bytecode instructions of each method. It doesn't give you back the Java source; it shows what the compiler actually produced. That makes it the best tool for answering questions like "what does a record really contain?", "what does + on strings compile to?" or "which Java version built this file?". In this lesson, you will learn the options of javap and how to read each part of its output.
"Bytecode and Platform Independence" used javap -c once. This lesson covers the tool completely.
Syntax
javap [options] ClassName
javap [options] path/to/File.class
javap -cp classes [options] com.school.Studentjavap takes a class name (looked up on the class path, like java does) or a path to a .class file. It also works on JDK classes: javap java.lang.Runnable.
Options
| Option | Description |
|---|---|
| (none) | It prints the public, protected and package-private members. |
-p (-private) | It is used to show all members, including private ones. |
-c | It prints the bytecode of every method. |
-v (-verbose) | It prints everything: version, flags, constant pool, stack sizes, attributes. |
-s | It prints the internal type descriptors of members. |
-l | It prints line number and local variable tables (use with -c). |
-constants | It shows the values of static final constants. |
-cp path | It sets where to look for classes. |
-sysinfo | It shows the file's path, size, date and checksum. |
Example: The Class We Will Inspect
public class Student {
public static final int MAX_MARKS = 100;
private final String name;
private int marks;
public Student(String name, int marks) {
this.name = name;
this.marks = marks;
}
public boolean passed() {
return marks >= 35;
}
private String label() {
return name + ": " + marks;
}
static double average(int[] all) {
long sum = 0;
for (int m : all) {
sum += m;
}
return (double) sum / all.length;
}
}Compile it with javac Student.java. Every output below is from that class file.
The Default Output and -p
Compiled from "Student.java"
public class Student {
public static final int MAX_MARKS;
public Student(java.lang.String, int);
public boolean passed();
static double average(int[]);
}Private members are hidden: this is the class as other classes see it. Add -p to see everything:
Compiled from "Student.java"
public class Student {
public static final int MAX_MARKS;
private final java.lang.String name;
private int marks;
public Student(java.lang.String, int);
public boolean passed();
private java.lang.String label();
static double average(int[]);
}Descriptors with -s
Inside a class file, types are written in a compact form called a descriptor:
public static final int MAX_MARKS;
descriptor: I
private final java.lang.String name;
descriptor: Ljava/lang/String;
public Student(java.lang.String, int);
descriptor: (Ljava/lang/String;I)V
public boolean passed();
descriptor: ()Z
static double average(int[]);
descriptor: ([I)D| Descriptor | Type |
|---|---|
B, C, S, I, J, F, D, Z | byte, char, short, int, long, float, double, boolean |
V | void |
Ljava/lang/String; | The class java.lang.String (L, the name with slashes, then ;) |
[I | int[]; each [ adds one array dimension |
(Ljava/lang/String;I)V | A method taking a String and an int, returning void |
Explanation: A method descriptor lists the parameter types inside the parentheses, with no separators, then the return type. ([I)D reads "takes an int[], returns a double". J is long (because L is taken) and Z is boolean (because B is byte). You saw these same strings in method handle types and in Class.getName() for arrays.
Bytecode with -c
public boolean passed();
Code:
0: aload_0
1: getfield #13 // Field marks:I
4: bipush 35
6: if_icmplt 13
9: iconst_1
10: goto 14
13: iconst_0
14: ireturnExplanation: aload_0 pushes this. getfield #13 replaces it with the value of this.marks; #13 is an entry in the constant pool, and the comment shows what it means. bipush 35 pushes 35. if_icmplt 13 jumps to position 13 if marks < 35. Otherwise iconst_1 pushes 1 (true) and goto 14 skips ahead. Position 13 pushes 0 (false). ireturn returns the value. The JVM has no boolean instructions: true and false are the int values 1 and 0. "Bytecode Basics in Java" explains the instructions in detail.
Everything with -v
Compiled from "Student.java"
public class Student
minor version: 0
major version: 69
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #8 // Student
super_class: #2 // java/lang/Object
interfaces: 0, fields: 3, methods: 4, attributes: 3
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Fieldref #8.#9 // Student.name:Ljava/lang/String;
...
#13 = Fieldref #8.#14 // Student.marks:I
#17 = InvokeDynamic #0:#18 // #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;
#23 = Integer 100| Part | Meaning |
|---|---|
major version: 69 | The class file version; 69 is Java 25 (the version is the Java release plus 44) |
flags | The class's modifiers as bits: ACC_PUBLIC, ACC_FINAL, ACC_INTERFACE, ACC_ABSTRACT and so on |
this_class, super_class | Constant pool entries naming the class and its parent |
| Constant pool | A numbered table of every name, type, string and number the class refers to |
Reading the constant pool: entries refer to each other. #13 is a Fieldref made of #8 (the class Student) and #14 (a name-and-type pair, marks:I). Instructions carry only the small number #13, and the JVM looks up the rest. Utf8 entries are the raw text. Entry #17 shows that name + ": " + marks in label() became an invokedynamic call to makeConcatWithConstants, and #23 is the constant 100.
For each method, -v also shows a line such as:
descriptor: ()Z
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1stack=2 is the deepest the operand stack gets, locals=1 is the number of local variable slots, and args_size=1 counts the arguments, where the hidden this is the one argument of an instance method.
Line Numbers and Local Variables with -l
LineNumberTable:
line 20: 0
line 21: 2
line 22: 24
line 21: 30
line 24: 36
LocalVariableTable:
Start Length Slot Name Signature
24 6 6 m I
0 43 0 all [I
2 41 1 sum JExplanation: The LineNumberTable maps bytecode positions to source lines; it is where the line numbers in stack traces come from. The LocalVariableTable appears only when the class is compiled with javac -g; debuggers use it to show variable names. Notice the slots: all is in slot 0 (a static method has no this), sum is in slot 1, and because a long takes two slots, the next variables start later.
What Javap Reveals
javap is the quickest way to see what the compiler adds for you. For record Point(int x, int y) { }:
Compiled from "Point.java"
final class Point extends java.lang.Record {
private final int x;
private final int y;
Point(int, int);
public final java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public int x();
public int y();
}One line of source produced a final class extending Record, two private final fields, a constructor, accessors, and toString, hashCode and equals. The same approach shows the hidden values() method of enums, bridge methods in generic classes, the synthetic methods behind lambdas, and how a switch on strings uses hashCode.
Common Mistakes
Adding .java or forgetting to compile. javap reads .class files; run javac first.
Expecting source code back. javap is a disassembler, not a decompiler. Tools such as CFR or the IDE's built-in decompiler try to rebuild source.
Forgetting -p. Private fields and methods, including compiler-generated ones, stay hidden without it.
Using -l alone. It needs -c, and local variable names need a class compiled with -g.
Interview Corner
What is javap and what is it used for?
javap is the JDK's class file disassembler. It prints a class's members (-p includes private ones), the bytecode of its methods (-c), and with -v the full contents of the class file: version, access flags, constant pool, descriptors, stack and local sizes, and attributes such as line number tables. It is used to learn how language features compile, verify which Java version a class targets, check what the compiler generated (bridge methods, synthetic members, record methods), and investigate performance or compatibility questions.
What is a method descriptor?
The JVM's internal text form of a method's parameter and return types, such as (Ljava/lang/String;I)V for a method taking a String and an int and returning void. Primitives are single letters (I, J for long, Z for boolean), classes are L + the slash-separated name + ;, and arrays are prefixed with [.
Practice
- Easy: Run
javap -p java.lang.String | grep -c "private"(or count by eye) to see how many private membersStringhas. - Harder: Write an enum
Level { LOW, HIGH }, compile it, and usejavap -p Levelto list every member the compiler generated. Explain what$VALUESandvalues()are for.
Key Takeaways
Was this tutorial helpful?