Build an Exhaustive String|Integer Labeler in Ceylon
Make every String|Integer variant explicit in Ceylon so the compiler catches missing branches before runtime.
13 Oct 2026, 16:31 UTC

Problem and takeaway
A function that accepts either text or a number often drifts into runtime checks: cast the value, hope it is the expected type, and throw an exception when it is not. If a caller later passes a new variant, the omission can reach production as an unhandled branch.
In Ceylon, declare one parameter with a union type such as String|Integer, then narrow each member with is inside an exhaustive switch. The compiler checks that every accepted variant is handled; removing a branch produces a compile error instead of a runtime exception.
Desired outcome
A small Ceylon function that accepts String|Integer, returns a String label for each variant, and fails to compile if any variant is left unhandled. No external parser library is required.
Prerequisites
- A Ceylon compiler installed on a JVM. Verify your distribution before embedding commands in a build, because tooling and JDK compatibility have changed over time.
- A source file with the
.ceylonextension. - Enough module setup to compile one module, such as a module descriptor and source layout expected by your Ceylon release.
- Write permission to the output directory if you compile to a local folder.
Write the labeling function
Declare the union parameter
A union type is one parameter whose value may belong to more than one type. It is not Java method overloading, so you cannot call String-only or Integer-only operations until you narrow the value.
shared String label(String|Integer value) {
switch (value)
case (is String) {
return "text: ``value``";
}
case (is Integer) {
return "number: ``value``";
}
}
Each case (is ...) branch narrows value to the named type inside that branch. In the String branch, value is a String; in the Integer branch, it is an Integer.
What the compiler checks when a case disappears
Delete the case (is Integer) block and recompile. A conforming Ceylon compiler reports a non-exhaustive switch because the Integer member of the union is no longer covered. The omission is caught at build time, not when an Integer reaches the function at runtime.
If your release requires a statement after an exhaustive switch, add a terminal throw or return after the switch. Avoid adding an else branch if you want the compiler to keep checking new union members, because else absorbs any value and silences the exhaustiveness diagnostic.
Handle nullable input safely
For nullable input, declare String? and narrow with exists or else. Do not assume non-null or use a cast-like assumption; that defeats Ceylon's null-safety checks.
shared String label(String? value) {
return if (exists value) then "text: ``value``" else "no value";
}
Pass null only after this handling exists. Without it, the compiler rejects the missing null case.
Comparison: cast versus union switch
| Approach | Compiler checks coverage | Risk if a variant is omitted |
|---|---|---|
| Direct cast to String or Integer | No | ClassCastException or similar failure at runtime |
Union type String|Integer with is switch | Yes | Compile error before the code can run |
Compile and run checks
Run these commands from the project root in a terminal or shell where the Ceylon compiler is available. No special permissions are required for a local compile, only write access to the output directory.
# Example only; replace CEYLON_HOME with your actual install path.
# Flags vary by Ceylon release; confirm with ceylon help compile.
CEYLON_HOME/bin/ceylon compile --module com.example.labeler/1.0.0
- Compile with both cases present. The build should succeed.
- Remove the
case (is Integer)block and recompile. Confirm the compiler reports a non-exhaustive switch. Exact wording varies by release. - Restore the block, compile again, then run the compiled code with a String such as
"hello"and an Integer such as42. Confirm both labels are returned. - Change the parameter to
String?, addexistsorelsehandling, and passnullonly after that change. - Compile twice from a clean output directory to catch stale artifacts.
Extend or recover safely
- When a new variant is added, extend the union and the
switchtogether so the compiler continues to check every branch. - For genuinely open inputs, use
case elseorelseand document the fallback behavior. - For nullable inputs, prefer
String?plusexistsorelseover casts or null assertions.
Limitations and version notes
- Exact diagnostic wording, numeric literal inference, and compiler or JDK support vary by Ceylon release. Pin the compiler in your build and re-check after upgrades.
- This guide describes well-established Ceylon behavior but does not verify a specific compiler version or error message for your environment.
- Treat command flags and module versions as placeholders until confirmed against your installed distribution.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.