Introduction to Javap
javap is the Java Class File Disassembler included with the JDK. It disassembles compiled Java class files and shows the structure, bytecode, and metadata of classes in a human-readable format.
What Javap Shows
- Class declaration and inheritance
- Fields and methods with signatures
- Bytecode instructions
- Constant pool information
- Access modifiers and annotations
1. Basic Javap Usage
Command Line Syntax
# Basic disassembly javap className # With full verbose output javap -v className # Disassemble with specific options javap -c -p -s className
Simple Example Class
// Simple class for disassembly demonstration public class SimpleCalculator { private int value; public SimpleCalculator() { this.value = 0; } public SimpleCalculator(int initialValue) { this.value = initialValue; } public int add(int number) { return this.value + number; } public static int multiply(int a, int b) { return a * b; } private void reset() { this.value = 0; } } Basic Disassembly Output
# Compile first javac SimpleCalculator.java # Basic disassembly javap SimpleCalculator
Output:
Compiled from "SimpleCalculator.java" public class SimpleCalculator { private int value; public SimpleCalculator(); public SimpleCalculator(int); public int add(int); public static int multiply(int, int); private void reset(); } 2. Common Javap Options
Complete Option Reference
| Option | Description |
|---|---|
-help | Print help message |
-version | Version information |
-v or -verbose | Verbose output including bytecode |
-l | Print line and local variable tables |
-public | Show only public classes and members |
-protected | Show protected/public classes and members |
-package | Show package/protected/public members (default) |
-p or -private | Show all classes and members |
-c | Disassemble bytecode |
-s | Print internal type signatures |
-sysinfo | Show system info (path, size, date, MD5 hash) |
-constants | Show static final constants |
3. Detailed Bytecode Analysis
Verbose Disassembly Example
javap -v -p SimpleCalculator
Partial Output:
Classfile /path/to/SimpleCalculator.class Last modified Dec 1, 2024; size 487 bytes MD5 checksum a1b2c3d4e5f6... Compiled from "SimpleCalculator.java" public class SimpleCalculator minor version: 0 major version: 55 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #5 // SimpleCalculator super_class: #6 // java/lang/Object interfaces: 0, fields: 1, methods: 4, attributes: 1 // Constant Pool (abbreviated) Constant pool: #1 = Methodref #6.#21 // java/lang/Object."<init>":()V #2 = Fieldref #5.#22 // SimpleCalculator.value:I #3 = Methodref #5.#23 // SimpleCalculator."<init>":(I)V // ... more constants // Fields private int value; descriptor: I flags: (0x0002) ACC_PRIVATE // Constructors and Methods public SimpleCalculator(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: iconst_0 6: putfield #2 // Field value:I 9: return public int add(int); descriptor: (I)I flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: getfield #2 // Field value:I 4: iload_1 5: iadd 6: ireturn
Bytecode Instruction Analysis
public class BytecodeExample { private String name; private int count; public BytecodeExample(String name) { this.name = name; this.count = 0; } public void increment() { count++; } public String process(int multiplier) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < multiplier; i++) { sb.append(name).append("-").append(count); } return sb.toString(); } } Disassemble with bytecode:
javap -c -p BytecodeExample
Output:
public class BytecodeExample { private java.lang.String name; private int count; public BytecodeExample(java.lang.String); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: aload_1 6: putfield #2 // Field name:Ljava/lang/String; 9: aload_0 10: iconst_0 11: putfield #3 // Field count:I 14: return public void increment(); Code: 0: aload_0 1: dup 2: getfield #3 // Field count:I 5: iconst_1 6: iadd 7: putfield #3 // Field count:I 10: return public java.lang.String process(int); Code: 0: new #4 // class java/lang/StringBuilder 3: dup 4: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V 7: astore_2 8: iconst_0 9: istore_3 10: iload_3 11: iload_1 12: if_icmpge 49 15: aload_2 16: aload_0 17: getfield #2 // Field name:Ljava/lang/String; 20: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 23: ldc #7 // String - 25: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 28: aload_0 29: getfield #3 // Field count:I 32: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 35: pop 36: iinc 3, 1 39: goto 10 42: astore 4 44: aload_2 45: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 48: areturn 49: aload_2 50: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 53: areturn } 4. Advanced Class Analysis
Analyzing Inheritance and Interfaces
// Complex class with inheritance and interfaces public interface Shape { double area(); double perimeter(); } public abstract class AbstractShape implements Shape { protected String color; public AbstractShape(String color) { this.color = color; } public String getColor() { return color; } } public class Circle extends AbstractShape { private double radius; public Circle(String color, double radius) { super(color); this.radius = radius; } @Override public double area() { return Math.PI * radius * radius; } @Override public double perimeter() { return 2 * Math.PI * radius; } public final double getRadius() { return radius; } } Analyze the Circle class:
javap -v -p Circle
Key Output Sections:
// Class hierarchy public class Circle extends AbstractShape flags: (0x0021) ACC_PUBLIC, ACC_SUPER // Interfaces implemented Interfaces: #0 = Shape // Method overrides and implementations public double area(); descriptor: ()D flags: (0x0001) ACC_PUBLIC Code: stack=6, locals=1, args_size=1 0: ldc2_w #2 // double 3.141592653589793d 3: dload_0 4: getfield #4 // Field radius:D 7: dmul 8: dload_0 9: getfield #4 // Field radius:D 12: dmul 13: dreturn public final double getRadius(); descriptor: ()D flags: (0x0011) ACC_PUBLIC, ACC_FINAL
Analyzing Annotations
import java.lang.annotation.*; import javax.validation.constraints.*; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @interface Author { String name(); String date(); } @Author(name = "John Doe", date = "2024-12-01") public class AnnotatedClass { @NotNull private String title; @Min(1) @Max(100) private int priority; @Deprecated public void oldMethod() {} public void newMethod(@NotNull String param) {} } Analyze annotations:
javap -v AnnotatedClass
Annotations Section:
// Class annotations RuntimeVisibleAnnotations: 0: #30(#31=s#32,#33=s#34) Author( name="John Doe" date="2024-12-01" ) // Field annotations private java.lang.String title; descriptor: Ljava/lang/String; flags: (0x0002) ACC_PRIVATE RuntimeVisibleAnnotations: 0: #35() javax.validation.constraints.NotNull // Method annotations public void oldMethod(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Deprecated: true public void newMethod(java.lang.String); descriptor: (Ljava/lang/String;)V flags: (0x0001) ACC_PUBLIC RuntimeVisibleParameterAnnotations: parameter 0: 0: #35() javax.validation.constraints.NotNull
5. Practical Use Cases and Examples
Debugging and Understanding Code
public class DebugExample { private static final int MAX_SIZE = 100; private int[] data; private int size; public DebugExample() { this.data = new int[MAX_SIZE]; this.size = 0; } public void add(int value) { if (size >= MAX_SIZE) { throw new IllegalStateException("Array full"); } data[size++] = value; } public int find(int value) { for (int i = 0; i < size; i++) { if (data[i] == value) { return i; } } return -1; } public void process() { int sum = 0; for (int i = 0; i < size; i++) { sum += data[i] * 2; } System.out.println("Sum: " + sum); } } Analyze for optimization:
javap -c -v DebugExample
Understanding Compiler Optimizations
public class OptimizationExample { // Analyze how compiler handles different constructs public int simpleLoop() { int sum = 0; for (int i = 0; i < 100; i++) { sum += i; } return sum; } public int stringConcatenation() { String result = ""; for (int i = 0; i < 10; i++) { result += i; // Inefficient concatenation } return result.length(); } public int optimizedString() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10; i++) { sb.append(i); } return sb.toString().length(); } public final int constantFolding() { return 10 + 20 * 30; // Should be compiled to constant } } Compare bytecode:
# Compare string concatenation vs StringBuilder javap -c OptimizationExample
Bytecode Comparison:
// Inefficient string concatenation public int stringConcatenation(); Code: 0: ldc #2 // String 2: astore_1 3: iconst_0 4: istore_2 5: iload_2 6: bipush 10 8: if_icmpge 37 11: new #3 // class java/lang/StringBuilder 14: dup 15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 18: aload_1 19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 22: iload_2 23: invokevirtual #6 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 26: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 29: astore_1 30: iinc 2, 1 33: goto 5 36: return // Efficient StringBuilder usage public int optimizedString(); Code: 0: new #3 // class java/lang/StringBuilder 3: dup 4: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 7: astore_1 8: iconst_0 9: istore_2 10: iload_2 11: bipush 10 13: if_icmpge 30 16: aload_1 17: iload_2 18: invokevirtual #6 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 21: pop 22: iinc 2, 1 25: goto 10 28: return
6. Advanced Javap Techniques
Analyzing Inner Classes
public class OuterClass { private String outerField = "outer"; public class InnerClass { private String innerField = "inner"; public void print() { System.out.println(outerField + " - " + innerField); } } public static class StaticNestedClass { private String staticField = "static"; public void print() { System.out.println(staticField); } } public void createLocalClass() { class LocalClass { public void print() { System.out.println("Local class"); } } new LocalClass().print(); } public Runnable createAnonymousClass() { return new Runnable() { @Override public void run() { System.out.println("Anonymous class"); } }; } } Analyze compiled inner classes:
# Compile first javac OuterClass.java # Analyze main class javap -v OuterClass # Analyze inner classes javap -v OuterClass\$InnerClass javap -v OuterClass\$StaticNestedClass javap -v OuterClass\$1LocalClass javap -v OuterClass\$1
Inner Class Characteristics:
// Inner class has reference to outer class public class OuterClass$InnerClass flags: (0x0020) ACC_SUPER InnerClasses: #24= #2 of #23; // InnerClass=class OuterClass$InnerClass of class OuterClass // Access to outer class field aload_0 getfield #1 // Field this$0:LOuterClass; getfield #2 // Field OuterClass.outerField:Ljava/lang/String;
Analyzing Synthetic Methods
public class SyntheticExample { private String privateField; public class Inner { public void accessPrivate() { // Accessing private field of outer class // generates synthetic accessor method System.out.println(privateField); } } } Synthetic methods in output:
// Synthetic accessor method generated by compiler static java.lang.String access$000(SyntheticExample); descriptor: (LSyntheticExample;)Ljava/lang/String; flags: (0x1008) ACC_STATIC, ACC_SYNTHETIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #1 // Field privateField:Ljava/lang/String; 4: areturn
7. Javap with Java Program
Programmatic Javap Execution
import java.io.*; import java.util.*; public class ProgrammaticJavap { /** * Execute javap command programmatically and capture output */ public static List<String> disassembleClass(String className, String... options) throws IOException, InterruptedException { List<String> command = new ArrayList<>(); command.add("javap"); // Add options if (options != null) { command.addAll(Arrays.asList(options)); } // Add class name command.add(className); ProcessBuilder pb = new ProcessBuilder(command); pb.redirectErrorStream(true); Process process = pb.start(); // Read output List<String> output = new ArrayList<>(); try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { output.add(line); } } int exitCode = process.waitFor(); if (exitCode != 0) { throw new IOException("javap failed with exit code: " + exitCode); } return output; } /** * Analyze multiple classes with different options */ public static void analyzeClasses(String... classNames) { for (String className : classNames) { try { System.out.println("\n=== Analyzing: " + className + " ==="); // Basic analysis List<String> basicOutput = disassembleClass(className); System.out.println("Basic structure:"); basicOutput.forEach(System.out::println); // Bytecode analysis System.out.println("\nBytecode:"); List<String> bytecodeOutput = disassembleClass(className, "-c"); bytecodeOutput.forEach(System.out::println); } catch (Exception e) { System.err.println("Failed to analyze " + className + ": " + e.getMessage()); } } } public static void main(String[] args) { // Example usage try { // Analyze current class analyzeClasses("ProgrammaticJavap"); // Analyze system classes List<String> stringOutput = disassembleClass("java.lang.String", "-c", "-p"); System.out.println("\n=== String class bytecode (first 20 lines) ==="); stringOutput.stream().limit(20).forEach(System.out::println); } catch (Exception e) { e.printStackTrace(); } } } Custom Bytecode Analyzer
import java.util.regex.*; import java.util.*; public class BytecodeAnalyzer { /** * Parse javap output to extract method information */ public static Map<String, MethodInfo> analyzeMethods(List<String> javapOutput) { Map<String, MethodInfo> methods = new LinkedHashMap<>(); Pattern methodPattern = Pattern.compile( "^(public|private|protected|).*? (\\w+)\\(.*\\)"); Pattern bytecodePattern = Pattern.compile("^\\s+\\d+:\\s+(\\w+)"); String currentMethod = null; List<String> currentBytecode = new ArrayList<>(); for (String line : javapOutput) { Matcher methodMatcher = methodPattern.matcher(line); if (methodMatcher.find()) { // Save previous method if (currentMethod != null) { methods.put(currentMethod, new MethodInfo(currentMethod, currentBytecode)); } // Start new method currentMethod = methodMatcher.group(2); currentBytecode = new ArrayList<>(); } Matcher bytecodeMatcher = bytecodePattern.matcher(line); if (bytecodeMatcher.find() && currentMethod != null) { currentBytecode.add(bytecodeMatcher.group(1)); } } // Don't forget the last method if (currentMethod != null) { methods.put(currentMethod, new MethodInfo(currentMethod, currentBytecode)); } return methods; } public static class MethodInfo { private final String name; private final List<String> instructions; private final Map<String, Integer> instructionCount; public MethodInfo(String name, List<String> instructions) { this.name = name; this.instructions = instructions; this.instructionCount = new HashMap<>(); // Count instruction frequency for (String instruction : instructions) { instructionCount.merge(instruction, 1, Integer::sum); } } // Getters public String getName() { return name; } public List<String> getInstructions() { return instructions; } public Map<String, Integer> getInstructionCount() { return instructionCount; } public int getInstructionCount() { return instructions.size(); } } public static void printAnalysis(Map<String, MethodInfo> methods) { System.out.println("=== BYTECODE ANALYSIS ==="); System.out.printf("Total methods analyzed: %d%n%n", methods.size()); for (MethodInfo method : methods.values()) { System.out.printf("Method: %s%n", method.getName()); System.out.printf(" Total instructions: %d%n", method.getInstructionCount()); System.out.printf(" Unique instructions: %d%n", method.getInstructionCount().size()); // Show most common instructions System.out.println(" Most frequent instructions:"); method.getInstructionCount().entrySet().stream() .sorted(Map.Entry.<String, Integer>comparingByValue().reversed()) .limit(5) .forEach(entry -> System.out.printf(" %s: %d%n", entry.getKey(), entry.getValue())); System.out.println(); } } } 8. Practical Tips and Best Practices
Common Javap Patterns
# 1. Quick method signature check javap -p MyClass # 2. Full bytecode analysis for debugging javap -c -v MyClass > bytecode.txt # 3. Compare two versions of a class javap -c OldVersion.class > old.txt javap -c NewVersion.class > new.txt diff old.txt new.txt # 4. Analyze system classes javap -c java.lang.String # 5. Check for synthetic methods and bridges javap -v MyClass | grep -E "(synthetic|bridge)" # 6. Analyze annotation processing javap -v AnnotatedClass | grep -A5 -B5 "Annotation"
Understanding Common Bytecode Patterns
public class BytecodePatterns { // Field access pattern private int field; public int getField() { return field; // aload_0, getfield, ireturn } // Method invocation pattern public void methodCall() { System.out.println("Hello"); // getstatic, ldc, invokevirtual } // Loop pattern public void loop() { for (int i = 0; i < 10; i++) { // iconst_0, istore_1, iload_1, bipush 10, if_icmpge // iinc 1, 1, goto } } // Exception handling pattern public void exceptionHandling() { try { riskyOperation(); } catch (Exception e) { handleException(e); } } // String concatenation pattern (Java 9+) public String stringConcat(String a, String b) { return a + b; // invokedynamic #0, makeConcatWithConstants } } Summary
Key Benefits of Javap:
- Debugging: Understand how Java code compiles to bytecode
- Performance Analysis: Identify inefficient bytecode patterns
- Learning: Learn Java internals and JVM operation
- Verification: Check compiler optimizations and synthetic methods
- Reverse Engineering: Understand third-party library behavior
Common Use Cases:
- Performance Optimization: Identify bytecode-level inefficiencies
- Compiler Behavior: Understand how language features compile
- Educational Purposes: Learn JVM internals
- Debugging: Troubleshoot mysterious behavior
- Security Analysis: Examine potentially malicious code
Limitations:
- Shows bytecode, not original source
- Requires understanding of JVM instruction set
- Verbose output can be overwhelming
- Doesn't show runtime behavior
Javap is an essential tool for Java developers who want to understand what's happening under the hood and optimize their code at the bytecode level.