Skip to Content
Course content

150: Migrating a Java Codebase to Scala

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

Can I actually mix Java and Scala in the same project, or is it "all or nothing"?

It's absolutely not all or nothing. In fact, trying to rewrite a massive production codebase in one giant leap is a recipe for a disaster. I've seen teams try that; it usually ends with a six-month "feature freeze" and a lot of stressed-out engineers.

Scala was designed for interoperability. You can have .java and .scala files sitting side-by-side in the same source folder, and they'll compile just fine. The JVM doesn't care which language wrote the bytecode. I usually recommend the "Strangler Pattern": leave the stable, boring Java code alone and write every new feature or bug fix in Scala. Over time, the Scala parts of your app grow, and the Java parts shrink.

// You can call this Java class from Scala without any special glue
public class LegacyUserStore {
    public User findById(String id) {
        // some old JDBC logic here
        return new User(id, "John Doe");
    }
}

// In your Scala file
val store = new LegacyUserStore()
val user = store.findById("123") // Works perfectly

If I use an IDE converter to flip a Java file to Scala, is that enough?

Short answer: No. Long answer: It's a great starting point to get the code compiling, but the result is usually what I call "Java written in Scala." It's syntactically correct, but it's not idiomatic. It'll still have mutable variables everywhere, clunky for loops, and probably a few too many null checks.

Look at this typical Java pattern for filtering a list of orders. An IDE converter will just swap the keywords, but it won't change the logic:

// "Java-style" Scala (Avoid this)
var highValueOrders = new scala.collection.mutable.ListBuffer[Order]()
for (order <= allOrders) {
  if (order.amount > 1000) {
    highValueOrders += order
  }
}

Once the code is converted, your real job is to refactor it into something that actually leverages the language. I'd rewrite that entire block as a single, declarative line:

val highValueOrders = allOrders.filter(_.amount > 1000)

How do I handle all the null values coming out of the Java side?

This is the part that actually bites you in production. Java loves null. Scala hates it. If you call a Java method that returns null and you treat it as a non-optional Scala type, you're just trading a NullPointerException in Java for one in Scala. Not exactly a win.

The trick is to "sanitize" the data at the boundary. The moment a value leaves a Java class and enters your Scala logic, wrap it in an Option. I usually do this by creating a small wrapper or using scala.jdk.CollectionConverters for lists, but for single objects, Option(...) is your best friend.

// Java method that might return null
public Customer getCustomer(int id) { 
    return database.find(id); // might return null
}

// Scala boundary logic
val customerOpt = Option(javaService.getCustomer(123))

customerOpt match {
  case Some(c) => println(s"Hello, ${c.name}")
  case None    => println("Customer not found")
}

By doing this, you force yourself (and your teammates) to handle the "missing" case explicitly, which is the whole point of migrating to Scala in the first place.




📋 Practical Task

Refactor the Legacy Payment Processor

You have been handed a legacy Java class called PaymentProcessor.java. It contains a method that calculates the total fee for a list of transactions, but it's written in a very imperative style with mutable state and potential nulls.

// Legacy Java Code
public class PaymentProcessor {
    public double calculateTotalFees(List<Transaction> transactions) {
        if (transactions == null) return 0.0;
        double total = 0.0;
        for (Transaction t : transactions) {
            if (t != null && t.getStatus().equals("COMPLETED")) {
                total += t.getFee();
            }
        }
        return total;
    }
}

Your Task:

  1. Convert this logic into a Scala object called PaymentProcessor.
  2. Eliminate the var and the for loop. Use a functional collection method (like filter and map, or foldLeft, or sum) to calculate the total.
  3. Ensure the code safely handles the case where the transactions list itself might be null (coming from Java) and where individual Transaction objects in the list might be null.
  4. The final result should be a clean, one-to-two line expression for the calculation.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.