How to create JavaBeans in Scala (to interact with Java libraries)

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 17.6, “How to create JavaBeans in Scala (to interact with Java libraries).”

Problem

In your Scala code, you need to interact with a Java class or library that only accepts classes that conform to the JavaBean specification.

Solution

Use the @BeanProperty annotation on your fields, also making sure you declare each field as a var.

The @BeanProperty annotation can be used on fields in a Scala class constructor:

import scala.reflect.BeanProperty

class Person(@BeanProperty var firstName: String,
             @BeanProperty var lastName: String) {
    override def toString = s"Person: $firstName $lastName"
}

It can also be used on the fields in a Scala class:

import scala.reflect.BeanProperty

class EmailAccount {
    @BeanProperty var username: String = ""
    @BeanProperty var password: String = ""
    override def toString = s"Email Account: ($username, $password)"
}

To demonstrate this, create an SBT project, then save the following code to a file named Test.scala in the root directory of the project:

package foo

import scala.reflect.BeanProperty

class Person(@BeanProperty var firstName: String,
             @BeanProperty var lastName: String) {
}

class EmailAccount {
    @BeanProperty var username: String = ""
    @BeanProperty var password: String = ""
}

This code shows how to use the @BeanProperty annotation on class constructor parameters, as well as the fields in a class.

Next, create a directory named src/main/java/foo, and save the following Java code in a file named Main.java in that directory:

package foo;

public class Main {
    public static void main(String[] args) {
        // create instances
        Person p = new Person("Regina", "Goode");
        EmailAccount acct = new EmailAccount();
        // demonstrate 'setter' methods
        acct.setUsername("regina");
        acct.setPassword("secret");
        // demonstrate 'getter' methods
        System.out.println(p.getFirstName());
        System.out.println(p.getLastName());
        System.out.println(acct.getUsername());
        System.out.println(acct.getPassword());
    }
}

This Java code demonstrates how to create instances of the Scala Person and EmailAccount classes, and access the JavaBean methods of those classes. When the code is run with sbt run, you’ll see the following output, showing that all the getter and setter methods work:

$ sbt run
[info] Running foo.Main
Regina
Goode
regina
secret

Discussion

You can see how the @BeanProperty annotation works by compiling a simple class and then disassembling it. First, save these contents to a file named Person.scala:

import scala.reflect.BeanProperty

class Person(@BeanProperty var name: String,
             @BeanProperty var age: Int) {
}

Then compile the class:

$ scalac Person.scala

After it’s compiled, disassemble it with the javap command:

$ 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 void setName(java.lang.String);
  public int age();
  public void age_$eq(int);
  public void setAge(int);
  public int getAge();
  public java.lang.String getName();
  public Person(java.lang.String, int);
}

As you can see from the disassembled code, the methods getName, setName, getAge, and setAge have all been generated because of the @BeanProperty annotation.

Note that if you declare your fields as type val, the “setter” methods (setName, setAge) won’t be generated:

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

Without these methods, your class will not follow the JavaBean specification.

As a final example, if the @BeanProperty annotation is removed from all fields, you’re left with this code:

class Person(var firstName: String, var lastName: String)

When you compile this code with scalac and then disassemble it with javap, you’ll see that no getter or setter methods are generated (except for those that follow the Scala convention):

Compiled from "Person.scala"
public class Person extends java.lang.Object{
  public java.lang.String firstName();
  public void firstName_$eq(java.lang.String);
  public java.lang.String lastName();
  public void lastName_$eq(java.lang.String);
  public Person(java.lang.String, java.lang.String);
}

See Also