Skip to Content
Course content

151: Interoperability Between Scala and Java Libraries

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

I was digging through an old project yesterday and realized I needed to pull in a Java library for some specialized PDF generation. It reminded me that while Scala runs on the JVM and claims "seamless" interoperability with Java, the reality is a bit more... textured. It's seamless until you hit a null or a java.util.List, and suddenly you're staring at a type mismatch that doesn't make sense.

Let's look at this together. I've got a hypothetical Java library called LegacyDataClient. It's written in old-school Java, and I want to use it in our Scala project to fetch some user metadata.

The "Null" Surprise

First, I'll just try to call a method to get a user's middle name. In Java, if a middle name doesn't exist, the method returns null. Here is my first attempt:

val client = new LegacyDataClient()
val middleName: String = client.getMiddleName("user_123")
println(s"Middle name is: ${middleName.toUpperCase}")

If the user has a middle name, this works perfectly. But for "user_123", who doesn't, I get a NullPointerException. This is the classic Java trap. Scala's type system tells me middleName is a String, but it's actually a null. The compiler didn't warn me because Java doesn't encode nullability in the type system.

I can't just trust Java libraries to be honest about their return types. To fix this, I'll wrap the result in a Scala Option. This is the idiomatic way to "sanitize" Java's nulls as soon as they enter your Scala code:

val middleNameOpt = Option(client.getMiddleName("user_123"))
middleNameOpt.foreach(name => println(s"Middle name is: ${name.toUpperCase}"))

Now, if it's null, Option() turns it into None. No crash, just a clean, functional path. I always do this at the boundary of my application—whenever a Java method returns a reference type, wrap it immediately.

Collection Friction

Next, I want to get a list of the user's recent login dates. The Java library returns a java.util.List[String].

val logins = client.getLoginDates("user_123")
// I want to filter out dates from 2022
val recentLogins = logins.filter(_.startsWith("2023")) 

Wait, this won't compile. I'm getting an error saying java.util.List doesn't have a filter method that takes a function. Why? Because java.util.List is a mutable Java collection, and it doesn't share the same API as scala.collection.immutable.List. They are entirely different beasts that just happen to have the same name.

I could use a while loop or an Iterator, but that's not why we use Scala. I need to convert this Java list into a Scala list. In Scala 2.13+, we use the scala.jdk.CollectionConverters package. Let's try that:

import scala.jdk.CollectionConverters._

val javaLogins = client.getLoginDates("user_123")
val scalaLogins = javaLogins.asScala.toList

val recentLogins = scalaLogins.filter(_.startsWith("2023"))
println(s"Recent logins: $recentLogins")

The .asScala method is a wrapper. It doesn't copy the whole list immediately (which is efficient), but it gives us the Scala-esque API we expect. Now I can use filter, map, and fold without blinking.

The Optional Paradox

Modern Java libraries (Java 8+) don't use null as much; they use java.util.Optional. This seems like it would play nice with Scala's Option, but it's actually a different type entirely. I'll try to use a method that returns a Java Optional:

val javaOpt = client.getAccountStatus("user_123") // Returns java.util.Optional[String]
val scalaOpt: Option[String] = javaOpt // Error: Type mismatch

It's frustrating, right? Two languages, both implementing the "Optional" pattern, but they aren't interchangeable. I can't just assign one to the other. Again, I have to go back to the converters.

I'll use CollectionConverters again, as it handles Optional as well:

import scala.jdk.CollectionConverters._

val javaOpt = client.getAccountStatus("user_123")
val scalaOpt = javaOpt.asScala 

println(scalaOpt.getOrElse("Unknown Status"))

By calling .asScala, I've converted the Java Optional into a Scala Option. At this point, the Java library is completely isolated. My core logic only deals with Scala types, and I've handled the "translations" at the very edge of the system. That's the secret to keeping your Scala code clean when you're forced to rely on Java dependencies.




📋 Practical Task

Exercise: Legacy Inventory System Bridge

You are integrating with a Java-based inventory library. You have been provided with the following Java class (imagine this is in a compiled JAR):

public class JavaInventoryManager {
    public List<String> getProductIds() {
        return Arrays.asList("PROD-001", "PROD-002", "PROD-003");
    }
    
    public String getProductDescription(String id) {
        if ("PROD-002".equals(id)) return null; // Simulating a missing description
        return "Description for " + id;
    }
    
    public java.util.Optional<Integer> getStockCount(String id) {
        if ("PROD-001".equals(id)) return java.util.Optional.of(10);
        return java.util.Optional.empty();
    }
}

Your Goal: Write a Scala program that uses JavaInventoryManager to produce a list of "Available Product Summaries".

Requirements:

  • Import the necessary CollectionConverters.
  • Fetch the product IDs and convert them to a Scala List.
  • For each product ID, fetch the description. Use Option() to handle the null and provide a default value of "No description available" if it is null.
  • Fetch the stock count. Convert the java.util.Optional to a Scala Option. Only include the product in your final list if the stock count is present (i.e., it's a Some).
  • The final output should be a List[String] where each string is formatted as: "ID: [id], Desc: [desc], Stock: [count]".
Rating
0 0

There are no comments for now.

to be the first to leave a comment.