Safer Java Interop in Ceylon with Union Types and Match
Learn how Ceylon’s union types and match expressions let you handle Java nulls safely at compile time, reducing runtime surprises when calling existing Java libraries.
29 Jun 2026, 03:11 UTC

The problem: Java nulls creep into Ceylon code
When you call a Java library from Ceylon, any method that can return null brings the risk of a NullPointerException at runtime. Ceylon’s type system aims to eliminate such surprises, but you still need a way to express that a value might be absent without littering your code with defensive checks.
Thesis: Union types + match give compile‑time safety for Java interop
Ceylon lets you model a value that is either a concrete type or the special Null type using a union type, e.g. File[]|Null. The language’s match expression then forces you to handle every case in the union, turning a potential runtime miss into a compile‑time error.
Union types in Ceylon
A union type is written with the pipe symbol (|). It represents a value that can be any of the listed types. The special Null type stands for Java’s null. Because unions are first‑class, the compiler tracks them through assignments, method returns, and generic parameters.
Match expressions exhaustively check unions
The match statement works like a switch but operates on types. Each branch specifies a type (or a literal) and the compiler verifies that all possible members of the union are covered. Missing a branch results in a compile‑time error, guaranteeing you won’t forget the Null case.
Worked example: Safely listing a directory
Suppose you want to use Java’s java.io.File.listFiles(), which returns File[] or null if the directory does not exist or is not readable. The following Ceylon code shows how to model the return value as a union and handle both outcomes with match.
import java.io.File
void listDirectory(String path) {
value dir = File(path);
// The union type expresses that listFiles may return null
value filesOrNull: File[]|Null = dir.listFiles();
match (filesOrNull) {
case (null) {
print("Directory ``" + path + "`` is inaccessible or does not exist.");
}
case (files) {
if (files.size == 0) {
print("Directory ``" + path + "`` is empty.");
} else {
print("Files in ``" + path + "``:");
for (f in files) {
print(" " + f.name);
}
}
}
}
}
// Example usage
void run() {
listDirectory("/tmp");
}
Where to run: Save the snippet to a file ListDir.ceylon, then execute from a terminal with the Ceylon SDK installed:
ceylon compile ListDir.ceylon– compiles to JVM bytecode.ceylon run ListDir– runs therunfunction.
Required permissions: Only read access to the directory you point to; no special privileges are needed.
Expected checks: If you remove the case (null) branch, the compiler will emit an error similar to "missing case for Null in match on File[]|Null", forcing you to handle the null possibility.
Trade‑off and limitation
While this pattern gives you static safety, Ceylon’s ecosystem is smaller than that of Kotlin or Scala. You may find fewer third‑party libraries, and IDE assistance (e.g., refactoring, autocomplete) in IntelliJ IDEA is less polished. Consequently, you might spend more time writing wrappers or adapting existing Java APIs.
Practical way to verify the benefit: After compiling, deliberately change the method call to one that can return null (e.g., point to a non‑existent directory) and observe that the program prints the informative message instead of throwing a NullPointerException. The absence of a runtime exception confirms that the union‑type/match approach succeeded.
Actionable closing
If you are integrating Java libraries into a JVM project and want to reduce null‑related bugs, consider modeling Java’s nullable returns as union types and using match to handle each case explicitly. The technique works with any Java method that may return null, and the compiler will remind you if you forget a case. Start with a small utility method, test the compile‑time guarantees, and expand the pattern as your codebase grows.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.