Behind the Bytecode: Exploring Java Features with javap tool

Mahendra Rao

When we develop applications, we often rely on a robust set of tools that the language’s development environment provides, which saves us from reinventing the wheel. In the case of Java also, the⁣ Java Development Kit (JDK) plays a vital role. It offers a comprehensive suite of tools, utilities, and commands that simplify the process of building, testing, and running Java applications, making our development faster and more efficient.

As a Java developer, I’m always fascinated by what happens behind the scenes when we compile my code into bytecode. This bytecode reveals how we represent and manage our code internally through the different components of the⁣ Java Virtual Machine (JVM). Exploring this process offers us deeper insights into Java’s architecture and helps us better understand memory management, performance, and runtime behavior.

In this blog post, let’s explore one such powerful tool from the JDK—the javap command-line utility—that enables us to inspect the bytecode and understand how the Java compiler translates our source code behind the scenes.

Before we dive into the nuances of the javap tool, let’s first understand some fundamental terminology by defining key concepts ourselves.

  • Compiler: In the computing world, a compiler is a software program that translates code written in one programming language into another, typically from a high-level language to a low-level language. It is most commonly used to convert source code into machine code, object code, or assembly language, enabling the creation of executable programs
  • Bytecode: also known as portable code or p-code, bytecode is a set of intermediate instructions that developers design for efficient execution by a software interpreter or virtual machine (such as the Java Virtual Machine). Unlike regular source code that humans can read, a compiler creates bytecode, a compact, machine-friendly version made up of numbers, constants, and references.
  • Interpreter: A computer program executes code written in a programming or scripting language directly, without compiling it again into a machine-language program.

Now that we’ve covered the key terms, we’ll explore the javap tool in more detail to understand how it works and why it’s useful.

What is JavaP?

Succinctly, we can describe the javap command as a tool that disassembles one or more class files to reveal their bytecode structure.

We’ll start by writing a simple HelloWorld program, compile it, and then use the javap tool with various options to explore the bytecode it generates.

Here is a simple HelloWorld program.

package com.bsmlabs.javapro;

public class JavaProHelloWorld {
    public static void main(String[] args) {
        System.out.println("Welcome to Java Pro!");
    }
}

The usage of the ⁣javap command is:

javap [options] classes... 

The javap tool lets you use different options to explore the bytecode versions of our Java code. Here you go, the list of options.

1. Help

  • -help or –help or -?: it shows a list of commands you can use with javap
(base) puneethsai@MacBook-Pro javapro % javap -h
Usage: javap <options> <classes>
where possible options include:
  --help -help -h -?               Print this help message
  -version                         Version information
  -v  -verbose                     Print additional information
  -l                               Print line number and local variable tables
  -public                          Show only public classes and members
  -protected                       Show protected/public classes and members
  -package                         Show package/protected/public classes
                                   and members (default)
  -p  -private                     Show all classes and members
  -c                               Disassemble the code
  -s                               Print internal type signatures
  -sysinfo                         Show system info (path, size, date, SHA-256 hash)
                                   of class being processed
  -constants                       Show final constants
  --module <module>, -m <module>   Specify module containing classes to be disassembled
  -J<vm-option>                    Specify a VM option
  --module-path <path>             Specify where to find application modules
  --system <jdk>                   Specify where to find system modules
  --class-path <path>              Specify where to find user class files
  -classpath <path>                Specify where to find user class files
  -cp <path>                       Specify where to find user class files
  -bootclasspath <path>            Override location of bootstrap class files
  --multi-release <version>        Specify the version to use in multi-release JAR files

GNU-style options may use = instead of whitespace to separate the name of an option
from its value.

Each class to be shown may be specified by a filename, a URL, or by its fully
qualified class name. Examples:
   path/to/MyClass.class
   jar:file:///path/to/MyJar.jar!/mypkg/MyClass.class
   java.lang.Object

2. Verbose

  • -v or -verbose: Prints the detailed information about the bytecode.
puneethsai@MacBook-Pro javapro % javap -v JavaProHelloWorld.class
Classfile /Users/puneethsai/devworkspace/jdk-examples/target/classes/com/bsmlabs/javapro/JavaProHelloWorld.class
  Last modified 26-Jul-2025; size 633 bytes
  SHA-256 checksum 51be93c41e88b32f32c5f74c7cc82e7ef8cf2642fb328452ecb960610d65f97b
  Compiled from "JavaProHelloWorld.java"
public class com.bsmlabs.javapro.JavaProHelloWorld
  minor version: 0
  major version: 68
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #21                         // com/bsmlabs/javapro/JavaProHelloWorld
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
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          // java/lang/System.out:Ljava/io/PrintStream;
   #8 = Class              #10            // java/lang/System
   #9 = NameAndType        #11:#12        // out:Ljava/io/PrintStream;
  #10 = Utf8               java/lang/System
  #11 = Utf8               out
  #12 = Utf8               Ljava/io/PrintStream;
  #13 = String             #14            // Welcome to Java Pro!
  #14 = Utf8               Welcome to Java Pro!
  #15 = Methodref          #16.#17        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #16 = Class              #18            // java/io/PrintStream
  #17 = NameAndType        #19:#20        // println:(Ljava/lang/String;)V
  #18 = Utf8               java/io/PrintStream
  #19 = Utf8               println
  #20 = Utf8               (Ljava/lang/String;)V
  #21 = Class              #22            // com/bsmlabs/javapro/JavaProHelloWorld
  #22 = Utf8               com/bsmlabs/javapro/JavaProHelloWorld
  #23 = Utf8               Code
  #24 = Utf8               LineNumberTable
  #25 = Utf8               LocalVariableTable
  #26 = Utf8               this
  #27 = Utf8               Lcom/bsmlabs/javapro/JavaProHelloWorld;
  #28 = Utf8               main
  #29 = Utf8               ([Ljava/lang/String;)V
  #30 = Utf8               args
  #31 = Utf8               [Ljava/lang/String;
  #32 = Utf8               MethodParameters
  #33 = Utf8               SourceFile
  #34 = Utf8               JavaProHelloWorld.java
{
  public com.bsmlabs.javapro.JavaProHelloWorld();
    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: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/bsmlabs/javapro/JavaProHelloWorld;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #13                 // String Welcome to Java Pro!
         5: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
    MethodParameters:
      Name                           Flags
      args
}
SourceFile: "JavaProHelloWorld.java"

The bytecode output might overwhelm you at first glance. To help you understand it better, I’ll walk you through some of the most important components and blocks of the bytecode.

Key Components of Bytecode

  1. ClassHeader: describes basic info about the class.
Classfile /Users/puneethsai/devworkspace/jdk-examples/target/classes/com/bsmlabs/javapro/JavaProHelloWorld.class
  Last modified 26-Jul-2025; size 633 bytes
  SHA-256 checksum 51be93c41e88b32f32c5f74c7cc82e7ef8cf2642fb328452ecb960610d65f97b
  Compiled from "JavaProHelloWorld.java"
  • Classfile: Path to the compiled .class code
  • Last Modified: display the timestamp when the class modified and its file size bytes
  • SHA-256: A unique fingerprint of the file and checksum for integrity verification
  • Compiled from: source java file name

2. Class Declaration:

public class com.bsmlabs.javapro.JavaProHelloWorld
  • public means it can be accessible anywhere

3. Minor and Major Version: Shows the Java version Compatibility

  minor version: 0
  major version: 68
  • Major version means it was compiled with Java 24.

4. High-Level Class Info:

flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #21 // com/bsmlabs/javapro/JavaProHelloWorld
super_class: #2 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1

flags: (0x0021) ACC_PUBLIC, ACC_SUPER

  • 0x0021: Hexadecimal representation of the class access flags
  • ACC_PUBLIC: The class is public; it can be accessible anywhere
  • ACC_SUPER: Enables new method invocation semantics that comes by default in modern JVMs
  • this_class: #21: Refers to the entry #21 in the constant pool. We will discuss about this shortly and it maps to fully qualified class name of current class, i.e., com/bsmlabs/javapro/JavaProHelloWorld
  • super_class: #2: Refers to the entry #2 in the constant pool and that entry java/lang/Object is inherited from Object, the root of all the Java classes.
  • interfaces: 0: this class DOESN’T implement any interfaces; hence it is 0.
  • fields: 0: this class DOESN’T have any instance or static fields; hence, it is 0.
  • methods: 2: This class contains two methods. One is the main method, i.e., main(String[] args) and another one with the default constructor.
  • attributes: 1. The source file itself is an attribute, and these attributes give the metadata about the file and are nowhere related to execution.

4. Constant Pool: It serves as a symbol table that retains all constants, method and field references, strings, and other elements utilized during JVM execution. For brevity, we will outline the essential indexes from the constant pool.

#TypeReferenceDescription
#1Methodref#2.#3Call to constructor java/lang/Object.<init>()
#2Class#4Class: java/lang/Object
#3NameAndType#5:#6Name + Type: <init>()V (Constructor)
#7Fieldref#8.#9Field: java/lang/System.out
#8Class#10Class: java/lang/System
#9NameAndType#11:#12Field name/type: out: PrintStream
#13String#14String constant: "Welcome to Java Pro!"
#15Methodref#16.#17Method call: PrintStream.println(String)
#16Class#18Class: java/io/PrintStream
#17NameAndType#19:#20Name + Type: println(String)
#21Class#22Current class: com/bsmlabs/javapro/JavaProHelloWorld

5. Methods: Each contains an access modifier, return type, name, and bytecode instructions

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #13                 // String Welcome to Java Pro!
         5: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return

These indexes from the constant pool and how they map to the actual code

// Puts System.out (PrintStream) on the operand stack.
System.out → getstatic → #7 → java/lang/System.out

//  Access static field, loads the string "Welcome to Java Pro!" on the stack.
"Welcome to Java Pro!" → ldc → #13 // load constant

// Calls println(String) which is an instance method on the PrintStream object with the string as an argument.
System.out.println(…) → invokevirtual → #15 → println(String)

Along with the methods or classes, you can see the below attributes.

LineNumberTable, LocalVariableTable, and StackMapTable describe line numbers and local vars, etc.

LineNumberTable:
    line 5: 0
    line 6: 8
LocalVariableTable:
     Start  Length  Slot  Name   Signature
       0       9     0  args   [Ljava/lang/String;
 

3. Local Number and Variables Tables

Command to execute: javap -l JavaProHelloWorld.class

> javap -l JavaProHelloWorld.class
Compiled from "JavaProHelloWorld.java"
public class com.bsmlabs.javapro.JavaProHelloWorld {
  public com.bsmlabs.javapro.JavaProHelloWorld();
    LineNumberTable:
      line 3: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       5     0  this   Lcom/bsmlabs/javapro/JavaProHelloWorld;

  public static void main(java.lang.String[]);
    LineNumberTable:
      line 5: 0
      line 6: 8
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       9     0  args   [Ljava/lang/String;
}

4. Access Modifiers, Packages

Command to execute: javap -public JavaProHelloWorld.class

similarly for other access modifiers, you can replace with -public with -protected, -private. And for package you can add -package

> javap -public JavaProHelloWorld.class
Compiled from "JavaProHelloWorld.java"
public class com.bsmlabs.javapro.JavaProHelloWorld {
  public com.bsmlabs.javapro.JavaProHelloWorld();
  public static void main(java.lang.String[]);
}

5. Display Disassemble code

Command to execute: javap -c JavaProHelloWorld.class

> javap -c JavaProHelloWorld.class
Compiled from "JavaProHelloWorld.java"
public class com.bsmlabs.javapro.JavaProHelloWorld {
  public com.bsmlabs.javapro.JavaProHelloWorld();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #13                 // String Welcome to Java Pro!
       5: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

BytecodeMeaning
aload_0Load this reference onto the stack (refers to the current object)
invokespecial #1Call the constructor of the superclass (Object.<init>())
returnExit the constructor

6. Display Method Descriptors

Command to execute: javap -s JavaProHelloWorld.class

> javap -s JavaProHelloWorld.class
Compiled from "JavaProHelloWorld.java"
public class com.bsmlabs.javapro.JavaProHelloWorld {
  public com.bsmlabs.javapro.JavaProHelloWorld();
    descriptor: ()V  
// () no parameters V Returns Void this is the constructor, so it takes no arguments and returns nothing

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    // [ -> This is an array
    // Ljava/lang/String; -> Qualified String object which is equivalent to String[]
    // V -> Return `void`
}

7. System Info

Displays system information like path, size, SHA-256 details using the following command: javap -sysinfo JavaProHelloWorld.class

 > javap -sysinfo JavaProHelloWorld.class
Classfile /Users/puneethsai/devworkspace/jdk-examples/target/classes/com/bsmlabs/javapro/JavaProHelloWorld.class
  Last modified 26-Jul-2025; size 633 bytes
  SHA-256 checksum 51be93c41e88b32f32c5f74c7cc82e7ef8cf2642fb328452ecb960610d65f97b
  Compiled from "JavaProHelloWorld.java"
public class com.bsmlabs.javapro.JavaProHelloWorld {
  public com.bsmlabs.javapro.JavaProHelloWorld();
  public static void main(java.lang.String[]);
}

8. Specify VM Option

With -J option, we can pass options directly to the JVM. For example

javap -J JavaProHelloWorld.class

> javap -J-Xmx256m JavaProHelloWorld.class

This tells the JVM to allocate a max of 256MB heap for running the javap tool.

CONCLUSION

The javap tool serves as a robust utility that enables developers to examine compiled .class files without the necessity of decompiling them. It offers significant insights into the architecture of Java classes, encompassing constructors, methods, bytecode instructions, constant pool entries, and additional elements. Whether you are acquiring knowledge about the internal workings of Java or troubleshooting intricate behaviors, javap is a crucial instrument for comprehending what the JVM perceives post-compilation. By utilizing various options such as -c, -v, and -p, you can investigate different levels of detail according to your requirements.

Happy Java Coding!

References:

https://docs.oracle.com/en/java/javase/11/tools/javap.html

Total
0
Shares
Previous Post

Debugging IN THE Dark

Next Post

05-2025 | Java 25 – (PART 2) – Special Edition

Related Posts