How to disassemble and decompile Scala code (javap, scalac, jad)

This is an excerpt from the 1st Edition of the Scala Cookbook (partially modified for the internet). This is Recipe 14.6, “How to disassemble and decompile Scala code.”

Problem

In the process of learning Scala, or trying to understand a particular problem, you want to examine the source code and bytecode the Scala compiler generates from your original source code.

Solution

You can use several different approaches to see how your Scala source code is translated:

  • Use the javap command to disassemble a .class file to look at its signature.
  • Use scalac options to see how the compiler converts your Scala source code to Java code.
  • Use a decompiler to convert your class files back to Java source code.

All three solutions are shown here.

Using javap

Because your Scala source code files are compiled into regular Java class files, you can use the javap command to disassemble them. For example, assume that you’ve created a file named Person.scala that contains the following source code:

class Person (var name: String, var age: Int)

If you compile that file with scalac, you can disassemble the resulting class file into its signature using javap, like this:

$ javap Person

Compiled from "Person.scala"
public class Person extends java.lang.Object implements scala.ScalaObject{
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public int age();
  public void age_$eq(int);
  public Person(java.lang.String, int);
}

This shows the signature of the Person class, which is basically its public API, or interface. Even in a simple example like this you can see the Scala compiler doing its work for you, creating methods like name(), name_$eq, age(), and age_$eq.

Using scalac print options

Depending on your needs, another approach is to use the “print” options available with the scalac command. These are demonstrated in detail in Recipe 3.1, “Looping with for and foreach”.

As that recipe shows, you begin with a file named Main.scala that has these contents:

class Main {
    for (i <- 1 to 10) println(i)
}

Next, compile this code with the scalac -Xprint:parse command:

$ scalac -Xprint:parse Main.scala

[[syntax trees at end of parser]] // Main.scala
package <empty> {
  class Main extends scala.AnyRef {
    def <init>() = {
      super.<init>();
      ()
    };
    1.to(10).foreach(((i) => println(i)))
  }
}

Recipe 3.1 demonstrates that the initial Scala for loop is translated into a foreach method call, as shown by this line in the compiler output:

1.to(10).foreach(((i) => println(i)))

If you want to see more details, use the -Xprint:all option instead of -Xprint:parse. For this simple class, this command yields more than 200 lines of out‐ put. A portion of the code at the end of the output looks like this:

class Main extends Object {
  def <init>(): Main = {
    Main.super.<init>();
    RichInt.this.to$extension0(scala.this.Predef.intWrapper(1),
    10).foreach$mVc$sp({
      (new anonymous class anonfun$1(Main.this): Function1)
    });
    ()
  }
};

As you can see, your beautiful Scala code gets translated into something quite different, and this is only part of the output.

Whereas scalac -Xprint:all prints a lot of output, the basic scalac -print command only prints the output shown at the very end of the -Xprint:all output. The scalac manpage states that this print option, “Prints program with all Scala-specific features removed.” View the manpage for the scalac command to see other -Xprint options that are available.

Use a decompiler

Depending on class versions and legal restrictions, you may be able to take this approach a step further and decompile a class file back to its Java source code representation using a Java decompiler tool, such as JAD. Continuing from the previous example, you can decompile the Main.class file like this:

$ jad Main

Parsing Main...Parsing inner class Main$$anonfun$1.class...
Generating Main.jad

The Main.jad file that results from this process contains the following Java source code:

import scala.*;
import scala.collection.immutable.Range;
import scala.runtime.*;
public class Main
{
  public Main()
  {
    RichInt$.MODULE$.to$extension0(Predef$.MODULE$.intWrapper(1),
      10).foreach$mVc$sp(new Serializable() {
      public final void apply(int i)
      {
        apply$mcVI$sp(i);
      }
      public void apply$mcVI$sp(int v1)
      {
        Predef$.MODULE$.println(BoxesRunTime.boxToInteger(v1));
      }
      public final volatile Object apply(Object v1)
      {
        apply(BoxesRunTime.unboxToInt(v1));
        return BoxedUnit.UNIT;
      }
      public static final long serialVersionUID = 0L;
    });
  }
}

Though you may have to be careful with legal issues when using a decompiler, when you’re first learning Scala, a tool like JAD or the Java Decompiler Project can really help to see how your Scala source code is converted into Java source code. Additionally, both Eclipse and IntelliJ offer decompiler plug-ins that are based on JAD or the Java Decompiler Project.

Discussion

Disassembling class files with javap can be a helpful way to understand how Scala works. As you saw in the first example with the Person class, defining the constructor parameters name and age as var fields generates quite a few methods for you.

As a second example, take the var attribute off both of those fields, so you have this class definition:

class Person (name: String, age: Int)

Compile this class with scalac, and then run javap on the resulting class file. You’ll see that this results in a much shorter class signature:

$ javap Person

Compiled from "Person.scala"
public class Person extends java.lang.Object implements scala.ScalaObject{
    public Person(java.lang.String, int);
}

Conversely, leaving var on both fields and turning the class into a case class significantly expands the amount of code Scala generates on your behalf. To see this, change the code in Person.scala so you have this case class:

case class Person (var name: String, var age: Int)

When you compile this code, it creates two output files, Person.class and Person$.class. Disassemble these two files using javap:

$ javap Person

Compiled from "Person.scala"
public class Person extends java.lang.Object implements scala.ScalaObject,scala."
Product,scala.Serializable{
  public static final scala.Function1 tupled();
  public static final scala.Function1 curry();
  public static final scala.Function1 curried();
  public scala.collection.Iterator productIterator();
  public scala.collection.Iterator productElements();
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public int age();
  public void age_$eq(int);
  public Person copy(java.lang.String, int);
  public int copy$default$2();
  public java.lang.String copy$default$1();
  public int hashCode();
  public java.lang.String toString();
  public boolean equals(java.lang.Object);
  public java.lang.String productPrefix();
  public int productArity();
  public java.lang.Object productElement(int);
  public boolean canEqual(java.lang.Object);
  public Person(java.lang.String, int);
}

$ javap Person$

Compiled from "Person.scala"
public final class Person$ extends scala.runtime.AbstractFunction2 implements "
scala.ScalaObject,scala.Serializable{
  public static final Person$ MODULE$;
  public static {};
  public final java.lang.String toString();
  public scala.Option unapply(Person);
  public Person apply(java.lang.String, int);
  public java.lang.Object readResolve();
  public java.lang.Object apply(java.lang.Object, java.lang.Object);
}

As shown, when you define a class as a case class, Scala generates a lot of code for you. This output shows the signature for that code. See Recipe 4.14, “Generating Boilerplate Code with Case Classes” for a detailed discussion of this code.

See Also

Sponsored by ...