The whole JVM ecosystem is available to Scala, but the seams need care.
Scala compiles to JVM bytecode, so any Java library is usable directly: import java.time.LocalDate and call methods as usual. Java classes are instantiated without new in Scala 3 too, Java static methods are called on the class name, and Java getters work with or without parentheses.
The seams to watch:
- Collections: Java and Scala collections are different types.
import scala.jdk.CollectionConverters.*adds.asScalaand.asJava, which wrap without copying. - Null: Java methods may return
null. Wrap results withOption(javaCall()). (The-Yexplicit-nullscompiler flag makes the type system track nullable Java types.) - Functional interfaces: Scala lambdas convert automatically to Java SAM types like
Runnable,Comparatororjava.util.function.Function.scala.jdk.FunctionConvertersandscala.jdk.FutureConverterscover other conversions, andscala.jdk.OptionConvertershandlesjava.util.Optional. - Checked exceptions don't exist in Scala; use
@throws[IOException]to declare them for Java callers. - Varargs: annotate with
@varargsto make a Scala varargs method callable from Java naturally.
Calling Scala from Java is possible but less pleasant: object members are reachable through static forwarders, default parameters aren't visible, and Scala collections look odd. For libraries consumed from Java, expose a thin Java-friendly facade.
Example
import java.time.{LocalDate, DayOfWeek}
import java.util.ArrayList
import scala.jdk.CollectionConverters.*
import scala.jdk.OptionConverters.*
val d = LocalDate.of(2026, 10, 17)
println(d.getDayOfWeek) // SATURDAY
println(d.plusDays(2).getDayOfWeek == DayOfWeek.MONDAY) // true
val jl = ArrayList[String]()
jl.add("pear"); jl.add("fig")
val sl: List[String] = jl.asScala.toList
println(sl.map(_.toUpperCase)) // List(PEAR, FIG)
jl.sort((a: String, b: String) => a.length - b.length) // lambda -> Comparator
println(jl) // [fig, pear]
val props = System.getProperties
println(Option(props.getProperty("no.such.key")).getOrElse("default")) // default
println(java.util.Optional.of(5).toScala) // Some(5)
println(sl.asJava.isInstanceOf[java.util.List[?]]) // true (a wrapper, no copy)Remember
- Java classes are used directly; static methods via the class name
scala.jdk.CollectionConvertersadds.asScala/.asJavawrappers- Wrap possibly-null Java results in
Option(...) - Lambdas convert to Java functional interfaces automatically
Watch out for
- Passing a Scala List to a Java API expecting java.util.List without
.asJava.