Skip to content
Java reflection 7 min read

javap Tool

The javap tool ships with every JDK and lets you peek inside any compiled .class file — reading method signatures, access modifiers, field types, and even raw JVM bytecode instructions. Whether you are debugging a mysterious jar, understanding what the compiler actually generates, or just satisfying your curiosity about how Java works under the hood, javap is your first stop.

What Is javap?

javap is a class file disassembler bundled in the JDK’s bin/ folder alongside javac and java. It reads a compiled .class file and prints its contents in a human-readable form. Unlike decompilers that try to reconstruct source code, javap shows you the structural and bytecode-level truth — exactly what the JVM will execute.

You can use it to:

  • Inspect method and field signatures (including private ones)
  • Verify that generics type erasure happened the way you expected
  • Read raw JVM bytecode mnemonics (aload, invokevirtual, ireturn, …)
  • Explore the constant pool — the JVM’s internal table of string literals, class references, and method descriptors
  • Understand how features like lambda expressions and string concatenation compile down to bytecode

Basic Syntax

javap [options] classname-or-classfile

You can pass either a fully qualified class name (if the class is on the classpath) or a direct path to a .class file:

# Using class name (must be on the classpath / in the current directory)
javap MyClass

# Using a file path
javap build/classes/MyClass.class

Your First javap Run

Compile a tiny class and immediately inspect it.

// Save as Hello.java, then: javac Hello.java
public class Hello {
    private String name;

    public Hello(String name) {
        this.name = name;
    }

    public void greet() {
        System.out.println("Hello, " + name);
    }
}

Running javap Hello (default — public API only) prints:

Compiled from "Hello.java"
public class Hello {
  public Hello(java.lang.String);
  public void greet();
}

That’s the public surface. The private field name does not appear yet. Add -private to see everything.

Key Flags

FlagWhat it shows
(none)Public and protected members only
-p or -privateAll members, including private
-cBytecode instructions for every method
-verboseFull detail: constant pool, stack frame sizes, source file, line numbers
-sInternal JVM type descriptors (Ljava/lang/String;, I, etc.)
-lLine number and local variable tables
-constantsShows static final constant values
-classpath <path>Where to look for classes

Showing All Members (-p)

javap -p Hello
Compiled from "Hello.java"
public class Hello {
  private java.lang.String name;
  public Hello(java.lang.String);
  public void greet();
}

Now the private field name appears.

Reading Bytecode (-c)

javap -c Hello
public void greet();
  Code:
     0: aload_0
     1: getfield      #7   // Field name:Ljava/lang/String;
     4: astore_1
     5: getstatic     #13  // Field java/lang/System.out:Ljava/io/PrintStream;
     8: aload_0
     9: getfield      #7   // Field name:Ljava/lang/String;
    12: invokedynamic #19,0 // InvokeDynamic makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
    17: invokevirtual #23  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    20: return

Tip: The number on the left (0:, 4:, etc.) is the bytecode offset in bytes within the method’s Code attribute, not a line number.

Notice invokedynamic for string concatenation — since Java 9, the compiler replaced the older StringBuilder approach with an invokedynamic call to StringConcatFactory. This is exactly the kind of insight javap reveals that you simply cannot see in source code.

Verbose Output (-verbose)

javap -verbose Hello

The verbose output adds the constant pool — a table of every string literal, class reference, method reference, and name/type descriptor the class uses. Here is an excerpt:

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     // Hello.name:Ljava/lang/String;
  ...

Note: Bytecode operands like #7 are indices into this constant pool. When the JVM executes getfield #7, it looks up entry #7 to find out which field to read.

Practical Examples

Verifying Type Erasure in Generics

Generics are erased at compile time. Use javap -p to confirm:

// Erased.java
import java.util.List;

public class Erased {
    public List<String> getNames() {
        return List.of("Alice", "Bob");
    }
}
javap -p Erased
public java.util.List getNames();

The <String> type parameter is completely gone from the bytecode — javap shows exactly what the JVM sees: raw List, not List<String>. This is the core meaning of type erasure.

Inspecting an Enum

Enums are compiled to a special class structure. Try javap -p Day on any enum and you will see auto-generated values(), valueOf(), and private $VALUES — all invisible in your source but very real in bytecode.

Examining Lambda Bytecode

// LambdaDemo.java
import java.util.function.Predicate;

public class LambdaDemo {
    public static void main(String[] args) {
        Predicate<String> notEmpty = s -> !s.isEmpty();
        System.out.println(notEmpty.test("hi"));
    }
}
javap -c LambdaDemo

The invokedynamic instruction for the lambda appears in the main method. The JVM uses LambdaMetafactory at runtime to spin up a class that implements Predicate — no anonymous inner class source code, no explicit class file written to disk. javap makes this visible.

Checking a Third-Party Jar

You are not limited to your own code. To inspect a class inside a jar:

# Extract first, then inspect
jar xf mylib.jar com/example/SomeClass.class
javap -c com/example/SomeClass.class

Or point -classpath at the jar:

javap -classpath mylib.jar com.example.SomeClass

This is invaluable for debugging classpath conflicts, verifying API compatibility, or understanding a dependency when you do not have its source.

Under the Hood

The .class File Format

Every .class file follows a rigidly defined binary format (specified in the JVM Specification). The structure is:

ClassFile {
    magic (0xCAFEBABE)    ← 4 bytes, identifies it as a Java class
    minor_version
    major_version          ← e.g. 65 = Java 21
    constant_pool_count
    constant_pool[]
    access_flags
    this_class
    super_class
    interfaces[]
    fields[]
    methods[]
    attributes[]           ← includes Code, LineNumberTable, StackMapTable, …
}

javap parses this binary structure and renders it as text. When you see major_version: 65 in -verbose output, that tells you the class requires Java 21 or newer (major_version maps to Java version as version - 44).

The Code Attribute

Each method’s body lives in an attribute called Code. It contains:

  • max_stack — maximum operand stack depth needed
  • max_locals — number of local variable slots
  • code[] — the actual bytecode bytes
  • exception_table[] — for try/catch ranges
  • Sub-attributes like LineNumberTable and LocalVariableTable (the source of -l output)

The JVM is a stack machine: most instructions push values onto or pop values from an operand stack. There are no general-purpose registers like in x86 — the stack is everything. Watching iload, iadd, and ireturn in javap -c output teaches you exactly how the JVM evaluates expressions.

Why javap Differs from a Decompiler

javapDecompiler (e.g. CFR, Fernflower)
OutputStructural / bytecode viewReconstructed Java source
AccuracyExact (reads binary directly)Best-effort (may be wrong)
Use caseJVM internals, debuggingReading third-party logic
Always availableYes (ships with JDK)No (third-party download)

Use javap when you care about what the JVM actually runs. Use a decompiler when you need to understand business logic in a jar without source.

Warning: Decompilers can silently produce incorrect reconstructions when obfuscation, unusual bytecode patterns, or synthetic methods are involved. Always cross-check with javap if exact behavior matters.

Quick Reference

# Public members only
javap MyClass

# All members (including private)
javap -p MyClass

# Bytecode for all methods
javap -c MyClass

# Bytecode + private + line numbers
javap -p -c -l MyClass

# Everything (constant pool, stack sizes, all members)
javap -verbose -p MyClass

# Class inside a jar
javap -classpath lib/utils.jar com.example.Util
  • Reflection API — Inspect and invoke class members at runtime using java.lang.reflect; javap shows you the metadata that Reflection reads.
  • JIT Compilation & Bytecode — Understand how the JVM takes the bytecode javap reveals and compiles it to native machine code.
  • JVM Architecture — The Class Loader, method area, and operand stack that execute the bytecode javap disassembles.
  • Generics — Use javap to see type erasure first-hand; what the compiler erases becomes clear at the bytecode level.
  • Lambda Expressionsjavap -c shows the invokedynamic instruction that powers every lambda; great for understanding the performance model.
  • Creating Objects Reflectively — Goes hand-in-hand with javap; inspect constructors with javap -p, then invoke them via Reflection.
Last updated June 13, 2026
Was this helpful?