Skip to Content
Course content

219: Wrapping Java Libraries for Idiomatic Scala Use

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was digging through some legacy code the other day and ran into a Java library we use for processing payment transactions. It’s a beast—stable as a rock, but written in a style that feels like a punch in the face when you're trying to write clean, functional Scala. It's full of null returns, mutable java.util.List objects, and those classic "Result" objects that you have to poll for errors.

Let's look at a snippet of this Java API—I'll call it LegacyPaymentGateway—and try to use it in Scala. I want to find a transaction by its ID and then get a list of all related tags.

The "Null" Surprise

// This is the Java API we're dealing with
// public class LegacyPaymentGateway {
//     public Transaction findTransaction(String id) { ... } // Returns null if not found
//     public java.util.List<String> getTags(Transaction tx) { ... }
// }

val gateway = new LegacyPaymentGateway()
val tx = gateway.findTransaction("TXN-123")

// In Scala, I'd expect an Option, but here tx is just... null.
println(tx.getId()) // Boom. NullPointerException if the ID doesn't exist.

Right away, we've got a problem. If I'm writing this in Scala, I don't want to pepper my code with if (tx != null) checks. That's exactly what Option was invented to kill. I could just wrap the call in Option(gateway.findTransaction("TXN-123")) every single time I use it, but that's tedious. If I'm calling this in twenty different places, I'm just asking for someone to forget the wrapper and crash the JVM.

Fighting with Java Collections

Now let's say I actually found the transaction. I want to filter the tags to find only the "HighPriority" ones. Watch what happens when I try to use Scala's collection methods on the Java return:

val tags = gateway.getTags(tx) 
// tags is a java.util.List[String]

// This won't compile:
val priorityTags = tags.filter(_ == "HighPriority") 
// Error: value filter is not a member of java.util.List

I've hit the "Collection Friction" wall. java.util.List isn't a scala.collection.immutable.List. To fix this, I could use scala.jdk.CollectionConverters, but again, I'm adding boilerplate to my business logic. My main logic is starting to look like a translation layer rather than actual payment processing.

Building the Idiomatic Bridge

Instead of fixing these issues at the call site, I'm going to wrap the entire Java library. The goal is to create a "Scala-flavored" version of the gateway that handles the nulls and the collections internally. I'll use a wrapper class that delegates to the Java instance.

import scala.jdk.CollectionConverters._

class PaymentGateway(private val javaGateway: LegacyPaymentGateway) {
  
  def findTransaction(id: String): Option[Transaction] = {
    // Wrap the null-returning Java method in an Option immediately
    Option(javaGateway.findTransaction(id))
  }

  def getTags(tx: Transaction): List[String] = {
    // Convert the Java List to a Scala List right here
    javaGateway.getTags(tx).asScala.toList
  }
}

// Now, look how the usage changes:
val gateway = new PaymentGateway(new LegacyPaymentGateway())

gateway.findTransaction("TXN-123") match {
  case Some(tx) => 
    val priorityTags = gateway.getTags(tx).filter(_ == "HighPriority")
    println(s"Found ${priorityTags.size} priority tags")
  case None => 
    println("Transaction not found")
}

Do you see the difference? The "ugly" parts—the Option(...) wrap and the .asScala.toList conversion—are now encapsulated. The rest of the application doesn't even know the LegacyPaymentGateway exists. It just sees a clean, idiomatic Scala API.

One last pro tip: if the Java library has a lot of static methods or a complex singleton pattern, I usually put these wrapper methods inside a Scala object or use a trait to define the interface. This makes it trivial to swap the real Java implementation for a mock during testing, which is something that's usually a nightmare with legacy Java libraries.




📋 Practical Task

Implementing a Scala Wrapper for JavaConfigManager

You are working with a Java library called JavaConfigManager. It has the following behavior (assume this Java code exists in your classpath):

public class JavaConfigManager {
    public String getProperty(String key) { 
        // Returns the value as a String, or null if the key doesn't exist
    }
    public java.util.Map<String, String> getAllConfigs() {
        // Returns a mutable Java Map of all configurations
    }
}

Your task is to create a Scala wrapper class named ConfigService that makes this library idiomatic. Your wrapper must:

  • Provide a method getProperty(key: String): Option[String] that converts the null return into a Scala Option.
  • Provide a method allConfigs: Map[String, String] that converts the java.util.Map into an immutable Scala Map using scala.jdk.CollectionConverters.

Write the ConfigService class and a small main method demonstrating that you can call getProperty and get an Option back, and that allConfigs allows you to use Scala's Map functions (like filter or mapValues) without any further conversion.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.