Coming from Java

Keal will feel close: static types, classes, generics, and a compiler that refuses what it cannot prove. The differences that matter are that nullability lives in the type, that there is no inheritance at all, and that a value's memory is counted rather than collected.

ConceptJavaKeal
Class with fieldsclass P { final int x; P(int x){this.x=x;} }record P(val x: Int)
Mutable holderclass C { int n; }class C(var n: Int)
Nullable@Nullable String s;val s: String?
Null checkif (s != null) s.length()if (s != null) { s.length }
Switchswitch (n) { case 0: … }when (n) { 0 -> … }
Instance testif (o instanceof Circle c)when (o) { is Circle(r) -> … }
Generic method<T> T first(List<T> xs)func first<T>(xs: List<T>): T
Interfaceinterface Ord { int compareTo(…); }trait Ord { func compareTo(other: Self): Int }
Lambdaxs.stream().map(x -> x * 2)xs.map({ it * 2 })
String format"hello " + name"hello ${name}"
DestructorAutoCloseable / try-with-resourcesproc deinit() { … }

What will surprise you

No inheritance, and that is deliberate

There is no extends. Traits with default methods and composition cover the same ground without the diamond, and records cover the data classes. If your design leans on a base class, it becomes a trait plus a field.

Null is a type, not a value you fear

String can never be null; String? can. The checker narrows an immutable binding after a test, so the cast Java makes you write is inferred.

Memory is counted, and you can see it

No collector, no pauses. keal layout prints every object's size and padding, deinit runs deterministically when the last reference dies, and a cycle is the one thing counting cannot free — write weak on the back edge.

You can still call Java

import java.time.LocalDate generates typed wrappers through JNI. The gateway is written in Keal; see the interop document.