How to wrap Scala traits so they can be used from Java code

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 17.7, “How to write Scala traits so they can be used from Java code.”

Problem

You’ve written a Scala trait with implemented methods and need to be able to use those methods from a Java application.

Solution

You can’t use the implemented methods of a Scala trait from Java, so wrap the trait in a class.

Assuming you have a Scala trait named MathTrait with a method named sum that you want to access from Java code, create a Scala class named MathTraitWrapper that wraps MathTrait:

// scala
package foo

// the original trait
trait MathTrait {
    def sum(x: Int, y: Int) =  x + y
}

// the wrapper class
class MathTraitWrapper extends MathTrait

In your Java code, extend the MathTraitWrapper class, and access the sum method:

// java
package foo;

public class JavaMath extends MathTraitWrapper {
    public static void main(String[] args) {
        new JavaMath();
    }
    public JavaMath() {
        System.out.println(sum(2,2));
    }
}

This code works as expected, printing the number 4 when it is run.

Discussion

A Java class can’t extend a Scala trait that has implemented methods. To demonstrate the problem, first create a trait with a simple implemented method named sum:

package foo

trait MathTrait {
    def sum(x: Int, y: Int) =  x + y
}

Next, to attempt to use this trait from Java, you have a choice of trying to extend it or implement it. Let’s first try to extend it:

package foo;
public class JavaMath extends MathTrait {}

By the time you finish typing that code in your IDE, you see the following compiler error message:

The type MathTrait cannot be the superclass of JavaMath;
a superclass must be a class

Next, you can attempt to implement the trait, but intuitively you know that won’t work, because in Java you implement interfaces, and this trait has implemented behavior, so it’s not a regular Java interface:

package foo;
public class JavaMath implements MathTrait {}

This code leads to the following compiler error:

The type JavaMath must implement the inherited abstract method
MathTrait.sum(int, int)

You could implement a sum method in your JavaMath class, but that defeats the purpose of trying to use the sum method that’s already written in the Scala MathTrait.

Other attempts

You can try other things, such as attempting to create an instance of the MathTrait and trying to use the sum method, but this won’t work either:

// java
package foo;

public static void main(String[] args) {
    MathTrait trait = new MathTrait();   // error, won't compile
    int sum = trait.sum(1,2);
    System.out.println("SUM = " + sum);
}

Trying to instantiate a MathTrait instance results in this compiler error:

foo.MathTrait is abstract; cannot be instantiated
[error] MathTrait trait = new MathTrait();
[error]                   ^

You may already know what the problem is, but to be clear, let’s see what class files are generated on the Scala side. In an SBT project, the class files are located in the following directory:

$PROJECT/target/scala-2.10.0/classes/foo

If you move into that directory and list the files, you’ll see that two files related to the Scala MathTrait trait have been created:

MathTrait.class
MathTrait$class.class

You can see the problem by disassembling these files with the javap command. First, the MathTrait.class file:

$ javap MathTrait
Compiled from "MathTrait.scala"
public interface foo.MathTrait{
    public abstract int sum(int, int);
}

Next, the MathTrait$class.class file:

$ javap MathTrait\$class
Compiled from "MathTrait.scala"
public abstract class foo.MathTrait$class extends java.lang.Object{
    public static int sum(foo.MathTrait, int, int);
    public static void $init$(foo.MathTrait);
}

The problem with trying to work with the Scala MathTrait from a Java perspective is that MathTrait looks like an interface, and MathTrait$class looks like an abstract class. Neither one will let you use the logic in the sum method.

Because MathTrait looks like just an interface, you realize you might be able to create a Java class that implements that interface, and then override the sum method:

// java
package foo;
public class JavaMath implements MathTrait {
    public int sum(int x, int y) {
        return x + y;
    }
    public static void main(String[] args) {
        JavaMath math = new JavaMath();
        System.out.println(math.sum(1,1));
    }
}

This does indeed work, but for the purposes of this recipe, it doesn’t really matter. The purpose of trying to use the trait was to use the behavior of the trait’s sum method, and there’s no way to do this from Java without creating a Scala wrapper class.

In a last desperate attempt, you might try to call super.sum(x,y) from your Java method, like this:

// java
public int sum(int x, int y) {
    return super.sum(x, y);
}

But that won’t work either, failing with the following error message:

cannot find symbol
[error] symbol  : method sum(int,int)
[error] location: class java.lang.Object
[error]     return super.sum(x,y);
[error]                 ^

The only way to solve the problem is to wrap the trait with a class on the Scala side, which was demonstrated in the Solution.

To summarize: If you’re writing a Scala API that will be used by Java clients, don’t expose traits that have implemented behavior in your public API. If you have traits like that, wrap them in a class for your Java consumers.

The Scala Cookbook

This tutorial is sponsored by the Scala Cookbook, which I wrote for O’Reilly:

You can find the Scala Cookbook at these locations:

Add new comment

The content of this field is kept private and will not be shown publicly.

Anonymous format

  • Allowed HTML tags: <em> <strong> <cite> <code> <ul type> <ol start type> <li> <pre>
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.