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.