Calling Scala Objects from JRuby in Java21

I’ve just gone a bit doo-lally figuring out how to make this work, so in the interests of saving the next poor git, if you’re upgrading from JRE8 to JRE21, JRuby-Scala integration changed and broke a few things. There’s no documentation apart from a single poorly-written line in a wiki page on github about how the change works, so good luck figuring out how this translates to real life examples:

// Add scala aliases for apply/update to roughly equivalent Ruby names
if (name.equals("apply")) {
  addUnassignedAlias("[]", assignedNames, Priority.ALIAS); 

After 223 attempts to call the [] operator in various ways trying to get it to call apply() behind the scenes and about twenty other approaches to getting the reference needed, I had to dive into how Scala gets compiled for the JVM, find that when the Scala code is for a Scala Object with a companion class (so basically a class with an associated singleton object), Scala compiles that to two classes on the JVM, one with the original name from the Scala code and one with a postfix’d dollarsign symbol.

To get the singleton reference for a Scala Object when it has a companion class, you need to access the MODULE$ field in the class with the $ postfix, but you can’t just call for that in JRuby as $ is a special character for accessing global variables amongst other things, so you have to go round the houses a bit.

It goes from this in JRE8 (actual package and class names changed to protect the guilty):

Foo::Bar.apply.addListener(self)

to this mess in JRE21:

bob = 'Java::Foo::Bar$'.split('::').inject(Object) { |n, c| n.const_get c }
fred = bob.java_class.get_field('MODULE$').get(nil)
fred.apply.addListener(self)

But, it works, and fred.apply gets back to the Object so you can call addListener() and register your callback function (this is from some inhouse library code, which looks like this, but obviously changed to skirt NDAs):

object Bar {
  private val A = new BarImpl
  def apply(): Bar = A
}

private[kafka] class BarImpl extends Bar {
  //Lots of internal guff redacted including a list of listeners
  @PublicAPI
  def addListener(listener: ListenerThingy) {
    val exists = listeners.contains(listener)
    if (!exists) {
      listeners.add(listener)
      notifyOtherThings(listener)
    }
  }

Next time, I choose an easier language. Like, say, assembler.

Tags: , , ,

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.