Ceylon Value Types: Boosting Performance with Immutable Value Objects
Learn how Ceylon’s value types let you create lightweight, immutable objects that save memory and improve performance, with a concrete example and a clear look at the trade‑offs you’ll face.
13 Aug 2026, 10:22 UTC

Concrete Problem: Heap‑Heavy Small Objects
In many JVM applications, a list of tiny data structures—points, coordinates, or simple DTOs—ends up on the heap, each with its own object header and reference. Even though the data itself is small, the overhead can push memory usage into the hundreds of megabytes and hurt garbage‑collector performance. Ceylon’s value types were designed to solve exactly this problem: they let you express small, immutable data as value objects that the compiler can inline, eliminating the heap allocation.
Thesis: Value Types Bring Compile‑Time Safety and Runtime Efficiency
Value types in Ceylon are immutable, value‑based types that provide structural equality out of the box and can be used directly as keys in maps or elements in sets without the need for a custom hashCode implementation. When you declare a type with the value keyword, the compiler treats it like a primitive: it can be represented in registers or stack slots, and the runtime can avoid allocating a separate heap object for each instance. This gives you the safety of type checking and the performance of inlined data.
What Are Value Types?
- Immutable – All fields must be declared
val(read‑only). Any attempt to mutate a field results in a compile‑time error. - Value‑based – Equality is structural: two instances with the same field values are considered equal, and the compiler generates
equalsandhashCodeaccordingly. - Inlineable – For small structures, the compiler can place the value directly in a register or on the stack, avoiding heap allocation.
- Serializable – Value types are automatically serializable to Ceylon’s JSON and Ceylon binary formats.
- Module‑safe – They can be exported and imported across modules with full type safety.
Declaring and Using a Value Class
The syntax is straightforward. The value class keyword declares an immutable, value‑based type. All fields must be val and the class cannot have any mutable state.
value class Point(
val x : Integer,
val y : Integer
) {
// Additional methods are allowed but must preserve immutability.
shared Integer sum() => x + y;
}
Once defined, you can instantiate it normally:
value p1 = Point(3, 4);
value p2 = Point(3, 4);
assert(p1 == p2); // structural equality
Because Point is a value type, you can use it as a key in a Map or store it in a Set without providing a custom hash code:
value map = HashMap<Point, String>();
map.put(p1, "origin");
assert(map.get(p1) == "origin");
Worked Example: Inlining vs. Heap Allocation
Suppose you need a list of 1,000,000 points. Using a traditional reference type would allocate 1,000,000 objects, each with an 8‑byte header and two 4‑byte fields, plus alignment padding. With a value type, the compiler can place each point directly in the array backing the list, reducing the memory footprint dramatically.
- Define
Pointas shown above. - Create a list:
value points = ArrayList<Point>(); for (i in 0..1_000_000) { points.add(Point(i, i * 2)); } - Run a simple memory profiler (e.g., VisualVM or JConsole) while the program is executing. You should observe that the memory used by the
pointslist is roughly the size of the array plus the two integer fields per element, with no separate object headers. - For comparison, replace
Pointwith a reference type (a normal class) and repeat the profiling. The memory consumption will be significantly higher.
Note: The profiler will still show the array backing the list, but the per‑element overhead is eliminated. The exact savings depend on the JVM and the size of the value type.
Trade‑offs and Limitations
- Immutability Constraint – Value types cannot hold mutable state. If you need to change a field, you must create a new instance.
- Java Interoperability – Value types are compiled to reference types when exposed to Java. You’ll need explicit wrappers or conversion methods to use them in Java code.
- Bytecode Size – Complex value types can generate large bytecode, potentially slowing class‑loading and startup.
- Future Migration – Value types were removed from later language proposals. Relying heavily on them may limit migration to newer JVM languages that do not support the same feature.
- JIT Optimization – Inlining large value types could bloat the generated machine code, affecting JIT performance for long‑running applications.
Actionable Closing
Use value types when you have small, immutable data structures that are created frequently and benefit from reduced heap allocation. Typical candidates include geometric points, simple DTOs, and domain‑specific value objects that never change after construction.
Before adopting value types in a legacy project, run a quick benchmark: create a list of 100,000 instances of the value type and a comparable list of reference objects, then measure memory usage and allocation rates. If the savings are significant and the immutability model fits your domain, value types can give you both safety and performance.
Remember to keep your codebase modular: export value types from a dedicated module, and provide clear documentation that these types are immutable and value‑based. This will help future maintainers understand the constraints and benefits.
Diagram: Value Type Characteristics
| Feature | Description |
|---|---|
| Immutable | All fields are val, no mutators allowed. |
| No Heap Allocation | Compiler can inline for small types. |
| Structural Equality | Equals and hashCode auto‑generated. |
| Java Interop | Wrapped as reference type for Java callers. |
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.