Java vs. Kotlin vs. Tinox vs. TypeScript

Detailed language comparison · syntax, performance, ecosystem, ergonomics

⚖️ Quick Comparison

Feature
Java
Kotlin
Tinox
TypeScript
First release
1995 (Sun Microsystems)
2016 (JetBrains)
2025 (Tino, open source)
2012 (Microsoft)
Paradigm
OOP, functional (since Java 8)
OOP, functional, multi-paradigm
OOP, functional, systems-level
OOP, functional, multi-paradigm
Type system
Static, nominal
Static, nominal, inference
Static, nominal, inference
Static, structural, inference
Execution
JVM (bytecode → JIT)
JVM / Native (LLVM) / JS
Native (LLVM IR → machine code)
Node.js / Deno / Bun (JS runtime)
GC
YES (G1, ZGC, Shenandoah…)
YES (JVM GC / Kotlin/Native ARC)
YES (own Tinox runtime)
YES (V8 GC)
Immutability by default
NO (final explicit)
YES (val/var)
YES (const/var)
PARTIAL (const / let)
Type inference
PARTIAL (var since Java 10)
YES (full)
YES (full)
YES (very strong)
Null safety
NO (NullPointerException)
YES (T? in the type system)
NO (null passes the typechecker; Option<T> in stdlib)
YES (strictNullChecks)
String interpolation
PARTIAL (text blocks, no ${expr})
YES ("Hi $name!")
YES ("Hi ${name}!")
YES (`Hi ${name}!`)
Generics
YES (type erasure)
YES (type erasure + reified)
YES (monomorphization)
YES (structural, compile time)
Lambdas
YES (since Java 8)
YES ({ x -> x * 2 })
YES (n => n * 2)
YES (n => n * 2)
Pattern matching
PARTIAL (switch expr., sealed classes)
YES (when, sealed classes, exhaustive)
YES (match statement)
NO (switch, no real match)
Extension functions
NO
YES (first-class)
NO
NO (prototype hacks)
Async / concurrency
YES (CompletableFuture, virtual threads)
YES (coroutines, flow, channels)
YES (async/await, spawn, channels)
YES (async/await, promises)
Defer
NO (try-with-resources)
NO (use { } as an alternative)
YES (defer {})
NO (try/finally)
Annotations / decorators
YES (runtime + compiler)
YES (@annotation, ksp, kapt)
YES (@inline, @deprecated, REST…)
PARTIAL (decorators, stage 3)
Startup time
Slow (JVM start)
Slow (JVM) / fast (native)
Instant (native binary)
Fast (Node.js / Bun)
Package manager
Maven / Gradle
Gradle (Kotlin DSL) / Maven
tinox install / add / package
npm / yarn / pnpm
Ecosystem
Huge (30 years of libraries)
Very large (JVM + Kotlin-specific)
Growing (50+ stdlib modules)
Gigantic (npm: 2M+ packages)

📝 Syntax & Basic Constructs

Hello World

Java

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

Kotlin

fun main() {
    println("Hello, World!")
}

Tinox

fn main() -> Int64
{
    println("Hello, World!");
    return 0;
}

TypeScript

console.log("Hello, World!");

Variables & Inference

Java

final int x = 42;           // immutable
int y = 10;                  // mutable
var name = "Alice";          // inference (Java 10+)
String msg = "Hi " + name + "!"; // no interpolation

Kotlin

val x: Int = 42              // immutable
var y: Int = 10              // mutable
val name = "Alice"           // inference everywhere
val msg = "Hi $name!"        // string interpolation
val expr = "2+2=${2 + 2}"    // expression in ${}

Tinox

const x: Int64 = 42;           // immutable
var y: Int64 = 10;           // mutable
const name = "Alice";          // inference everywhere
const msg = "Hi ${name}!";     // string interpolation

TypeScript

const x: number = 42;        // immutable
let y: number = 10;          // mutable
const name = "Alice";        // inference
const msg = `Hi ${name}!`;   // template literal

Loops & Ranges

Java

for (int i = 0; i < 5; i++) {
    System.out.println(i);
}
for (String s : list) {
    System.out.println(s);
}
IntStream.range(0, 5)
         .forEach(System.out::println);

Kotlin

for (i in 0 until 5) println(i) // 0..4
for (i in 0..5) println(i)      // 0..5
for (s in list) println(s)

(0 until 5).forEach { println(it) }
(1..5 step 2).forEach { println(it) }

Tinox

for i in 0..5        // exclusive: 0,1,2,3,4
{
    println(i);
}
for i in 0...5       // inclusive: 0,1,2,3,4,5
{
    println(i);
}
for ch in "hello"    // character iteration
{
    print(ch);
}

TypeScript

for (let i = 0; i < 5; i++) {
    console.log(i);
}
for (const s of list) {
    console.log(s);
}
[...Array(5).keys()]
    .forEach(i => console.log(i));

Tinox — Syntax Advantages

  • Less boilerplate than Java (no public class wrapper)
  • const/var makes mutability immediately visible
  • String interpolation built into the language
  • Ranges (0..5, 0...5) — no iterator API needed
  • Allman-style formatter enforces a consistent style

Tinox — Syntax Disadvantages

  • Kotlin has shorter lambdas and less ceremony
  • No REPL (still planned)
  • Allman style can be polarizing
  • Smaller community, fewer tutorials

🧬 Types & Generics

Generics

Java

// Type erasure: T → Object at runtime
public class Box<T> {
    private T value;
    public Box(T value) { this.value = value; }
    public T get() { return value; }
}

Box<Integer> b = new Box<>(42);
// Wildcards: Box<? extends Number>
// No Box<int>, only Box<Integer>

Kotlin

// Type erasure + reified for inline functions
class Box<T>(val value: T)

val b = Box(42)
println(b.value)

// Variance annotations:
// out T (covariant), in T (contravariant)
// reified with inline fun:
inline fun <reified T> isType(x: Any) = x is T

Tinox

// Monomorphization: real type is preserved
class Box<T>
{
    value: T;

    fn get() -> T
    {
        return this.value;
    }
}

const b = new Box<Int64>(42);
// Primitives usable directly (no boxing)

TypeScript

// Structural type system, erased at compile time
class Box<T> {
    constructor(private value: T) {}
    get(): T { return this.value; }
}

const b = new Box<number>(42);
// Constraints: Box<T extends number>
// Conditional types: T extends U ? X : Y

Collections

Java

List<String> list = new ArrayList<>();
list.add("hello");
list.size();
list.get(0);

Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.get("a");

Kotlin

val list = mutableListOf("hello")
list.add("world")
list.size
list[0]

val map = mutableMapOf("a" to 1)
map["b"] = 2
map["a"]

// Read-only:
val ro = listOf(1, 2, 3)

Tinox

const arr = ["hello"];
arr.push("world");
arr.len();
arr[0];

const m = @{"a" => 1};
m.insert("b", 2);
m.get("a");
// typed:
const m: Map<String, Int64> = Map::new();

TypeScript

const list: string[] = [];
list.push("hello");
list.length;
list[0];

const map = new Map<string, number>();
map.set("a", 1);
map.get("a");

const obj: Record<string, number> = { a: 1 };

Tinox Generics — Advantages

  • Monomorphization: no type-erasure overhead
  • Primitives directly as type parameters (no Integer vs. int)
  • Map literal syntax (@{"k" => v}) is more compact
  • Array builtins (first, last, join) without imports

Tinox Generics — Disadvantages

  • Monomorphization can increase binary size
  • Wildcards (? extends T) and variance annotations are missing
  • No reified / reflection access to type information
  • Bounded type parameters still limited

🏗️ OOP, Classes & Inheritance

Inheritance

Java

public abstract class Animal {
    protected String name;
    public Animal(String name) { this.name = name; }
    public abstract String speak();
}

public class Dog extends Animal {
    public Dog(String name) { super(name); }

    @Override
    public String speak() {
        return "Woof! I am " + name;
    }
}

Animal a = new Dog("Rex");
System.out.println(a.speak());

Kotlin

abstract class Animal(protected val name: String) {
    abstract fun speak(): String
}

class Dog(name: String) : Animal(name) {
    override fun speak() = "Woof! I am $name"
}

val a: Animal = Dog("Rex")
println(a.speak())

Tinox

class Animal
{
    name: String;

    fn speak() -> String
    {
        return "...";
    }
}

class Dog extends Animal
{
    fn speak() -> String
    {
        return "Woof! I am ${this.name}";
    }
}

const a: Animal = new Dog();
a.name = "Rex";
println(a.speak());

TypeScript

abstract class Animal {
    constructor(protected name: string) {}
    abstract speak(): string;
}

class Dog extends Animal {
    speak(): string {
        return `Woof! I am ${this.name}`;
    }
}

const a: Animal = new Dog("Rex");
console.log(a.speak());

Data Classes & Records

Java

// Java 16+: records
public record Point(int x, int y) {}

// auto-generated: equals, hashCode,
// toString, accessor methods x(), y()

Point p = new Point(3, 4);
System.out.println(p.x()); // 3

Kotlin

// data class: equals, hashCode,
// toString, copy() auto-generated
data class Point(val x: Int, val y: Int)

val p = Point(3, 4)
println(p.x) // 3

// Copy with change:
val p2 = p.copy(y = 10)

Tinox

// immutable: unchangeable value object
// new() is auto-generated
immutable Point(x: Int64, y: Int64)

const p = new Point(3, 4);
println(p.x); // 3

// Fields via field access (read-only)

TypeScript

// No built-in record concept
// Interface + object as a workaround:
interface Point { readonly x: number; readonly y: number; }

const p: Point = { x: 3, y: 4 };
console.log(p.x); // 3

// Class alternative with readonly:
class Point {
    constructor(readonly x: number, readonly y: number) {}
}

Interfaces

Java

public interface Printable {
    String toString();
    // Default methods possible:
    default void print() {
        System.out.println(toString());
    }
}

public class Point implements Printable {
    int x, y;
    @Override
    public String toString() {
        return "(" + x + ", " + y + ")";
    }
}

Kotlin

interface Printable {
    fun label(): String
    // Default implementation possible:
    fun print() = println(label())
}

data class Point(val x: Int, val y: Int) : Printable {
    override fun label() = "($x, $y)"
}

// Extension function (without an interface):
fun Point.describe() = "x=$x y=$y"

Tinox

interface Printable
{
    fn toString() -> String;
}

class Point implements Printable
{
    var x: Int64;
    var y: Int64;

    fnc new(x: Int64, y: Int64) -> Point
    {
        return Point { x: x, y: y };
    }

    fn toString() -> String
    {
        return "(${this.x}, ${this.y})";
    }
}

TypeScript

interface Printable {
    toString(): string;
}

class Point implements Printable {
    constructor(
        public x: number,
        public y: number
    ) {}

    toString(): string {
        return `(${this.x}, ${this.y})`;
    }
}
// Structural typing: duck typing

Kotlin OOP — Advantages

  • data class auto-generates equals/hashCode/copy
  • Extension functions without inheritance or wrappers
  • Sealed classes for exhaustive pattern matching
  • No constructor boilerplate (primary constructor)
  • Full Java interoperability

Tinox OOP — Advantages

  • fn (instance) vs. fnc (static) immediately visible
  • immutable for unchangeable value objects
  • Vtable-based interface dispatch
  • Less boilerplate than Java

λ Functional Programming

Lambdas & Higher-Order Functions

Java

// Function types via java.util.function.*
Function<Integer, Integer> doubled = n -> n * 2;

static int apply(int x,
    Function<Integer, Integer> f) {
    return f.apply(x);
}
apply(21, n -> n * 2); // 42

List.of(1,2,3).stream()
    .map(n -> n * 2)
    .filter(n -> n > 2)
    .toList();

Kotlin

// Function types natively: (Int) -> Int
val doubled: (Int) -> Int = { n -> n * 2 }
// or short form with it:
val doubled2: (Int) -> Int = { it * 2 }

fun apply(x: Int, f: (Int) -> Int) = f(x)
apply(21) { it * 2 } // 42 (trailing lambda)

listOf(1, 2, 3)
    .map { it * 2 }
    .filter { it > 2 }

Tinox

fn apply(x: Int64,
    f: fnc(Int64) -> Int64) -> Int64
{
    return f(x);
}

const result = apply(21, n => n * 2); // 42

// Lambda as a value
const square: fnc(Int64) -> Int64
    = fn(x: Int64) -> Int64
{
    return x * x;
};

TypeScript

const doubled = (n: number) => n * 2;

function apply(x: number,
    f: (n: number) => number): number {
    return f(x);
}
apply(21, n => n * 2); // 42

[1, 2, 3]
    .map(n => n * 2)
    .filter(n => n > 2);

Pattern Matching

Java

// Java 21: pattern matching switch
Object obj = ...;
String result = switch (obj) {
    case Integer i -> "int: " + i;
    case String s  -> "str: " + s;
    default        -> "other";
};

// Enum switch
Direction d = Direction.NORTH;
switch (d) {
    case NORTH -> System.out.println("N");
    case SOUTH -> System.out.println("S");
    default    -> {} // required
}

Kotlin

// when as an expression (exhaustive with sealed)
sealed class Shape
class Circle(val r: Double) : Shape()
class Rect(val w: Double, val h: Double) : Shape()

fun area(s: Shape) = when (s) {
    is Circle -> Math.PI * s.r * s.r
    is Rect   -> s.w * s.h
    // no else needed — sealed is exhaustive
}

// when without an argument as an if-chain:
when {
    x < 0  -> println("negative")
    x == 0 -> println("zero")
    else   -> println("positive")
}

Tinox

enum Direction
{
    North; South; East; West;
}

fnc turn(d: Direction) -> Direction
{
    match d
    {
        North => return East;
        East  => return South;
        South => return West;
        West  => return North;
    }
}
// Exhaustiveness is checked

TypeScript

type Direction = "North"|"South"|"East"|"West";

function turn(d: Direction): Direction {
    switch (d) {
        case "North": return "East";
        case "East":  return "South";
        case "South": return "West";
        case "West":  return "North";
    }
}

// Discriminated unions:
type Shape =
    | { kind: "circle"; r: number }
    | { kind: "rect"; w: number; h: number };

Kotlin Functional — Advantages

  • Trailing-lambda syntax: f(x) { it * 2 } is very readable
  • Scope functions: let, run, with, apply, also
  • Sealed classes + when = complete pattern matching
  • Sequence for lazy evaluation (no Stream needed)

Tinox Functional — Advantages

  • Function types directly (no java.util.function import)
  • match is exhaustive over enums
  • Compact lambda syntax (n => n * 2)
  • defer for clean resource management

⚡ Concurrency & Async

Java

// Virtual threads (Java 21)
try (var exec =
    Executors.newVirtualThreadPerTaskExecutor()) {
    exec.submit(() -> process());
}

// CompletableFuture
CompletableFuture<Integer> f =
    CompletableFuture
        .supplyAsync(() -> fetchData(21))
        .thenApply(n -> n * 2);
int result = f.get(); // blocks

// Channel: BlockingQueue
BlockingQueue<Integer> ch =
    new LinkedBlockingQueue<>();
ch.put(99);
int v = ch.take();

Kotlin

// Coroutines: lightweight threads
suspend fun fetchData(id: Int): Int {
    delay(100) // non-blocking
    return id * 2
}

fun main() = runBlocking {
    val result = async { fetchData(21) }
    println(result.await()) // 42

    // Channels (Go-like)
    val ch = Channel<Int>()
    launch { ch.send(99) }
    println(ch.receive()) // 99

    // Flow for reactive streams
    flow { emit(1); emit(2) }
        .collect { println(it) }
}

Tinox

async fn fetchData(id: Int64) -> Int64
{
    return id * 2;
}

fn main() -> Int64
{
    const handle = spawn fetchData(21);
    const result = await handle; // 42
    println(result);

    // Channels (Go-inspired)
    const ch = channel;
    send ch -> 99;
    const v = recv ch;
    println(v);

    return 0;
}

TypeScript

async function fetchData(id: number)
    : Promise<number> {
    return id * 2;
}

async function main() {
    const result = await fetchData(21); // 42
    console.log(result);

    // Parallel execution
    const [a, b] = await Promise.all([
        fetchData(1),
        fetchData(2),
    ]);

    // No native channel
}

Kotlin Concurrency — Advantages

  • Coroutines: lightweight, no thread overhead
  • Structured concurrency (CoroutineScope)
  • Flow for reactive data streams
  • Channels and select natively available

Tinox Concurrency — Advantages

  • async/await built into the language (no framework needed)
  • Channels as a first-class concept
  • spawn is more intuitive than CompletableFuture chains
  • No import from java.util.concurrent

🚀 Memory Management & Performance

Java

// JVM-based: everything on the heap
// GC: G1, ZGC, Shenandoah
// Startup: ~100–500ms (JVM start)
// Peak performance: very high (JIT)
// Memory: JVM heap + MetaSpace
// GraalVM native-image as an option

Kotlin

// JVM: identical to Java (same GC)
// Kotlin/Native: LLVM backend, no GC (ARC)
// Kotlin/JS: transpiles to JavaScript

// JVM startup: ~150–500ms
// Native startup: instant
// Native: no JVM overhead
// Native GC: ARC (automatic reference counting)

Tinox

// LLVM backend: AOT to machine code
// GC: own Tinox runtime (C, pthread)
// Startup: instant (native binary)
// Performance: comparable to C
// Memory: no JVM overhead
// No GraalVM needed

TypeScript

// Transpiles to JS → V8 (Node.js)
// GC: V8 generational GC
// Startup: ~50–200ms (Node.js)
//          very fast with Bun
// Performance: good for I/O workloads
// Memory: V8 heap overhead

Tinox — Performance Advantages

  • No JVM start overhead (instant startup)
  • LLVM O2/O3 directly applicable
  • No boxing/unboxing for primitives in generics
  • No MetaSpace overhead
  • Ideal for CLI tools, microservices, embedded

Tinox — Performance Disadvantages

  • JVM JIT after warmup is often very high (Java/Kotlin)
  • Kotlin/Native also offers instant startup
  • Tinox GC not yet as mature as G1/ZGC
  • No profiling tooling like Java Flight Recorder

📦 Standard Library

Java (JDK)

// Collections: java.util.*
// I/O: java.io, java.nio
// Networking: java.net, java.net.http
// Time: java.time (since Java 8)
// Security: java.security
// Regex: java.util.regex
// Math: java.lang.Math
// JSON: no standard (Jackson, Gson)
// HTTP client: java.net.http (Java 11+)

Kotlin (stdlib + kotlinx)

// kotlin.collections: List, Map, Set
// kotlin.io: file utilities
// kotlin.text: string extensions
// kotlinx.coroutines: async/await, Flow
// kotlinx.serialization: JSON, CBOR, Proto
// kotlinx.datetime: date/time
// ktor: HTTP client + server (multi-platform)
// Exposed: SQL ORM
// Arrow: functional types (Option, Either)

Tinox (tinox-core)

// HTTP:     http_server, rest_framework
// Data:     json, csv, xml, regex
// Security: crypto, jwt, bcrypt
// Collections: collections, queue, set
// System:   fs, io, env, process
// Utils:    math, string_utils, uuid, date
// Async:    cron, events, pool, cache, pubsub
// 50+ modules as .tnx files

TypeScript / Node.js

// HTTP:     express, fastify, hono
// Data:     JSON native, csv-parse, xml2js
// Security: jsonwebtoken, bcrypt, node:crypto
// Collections: native arrays, maps, sets
// System:   node:fs, node:path, node:process
// Utils:    date-fns, uuid, zod, lodash
// Async:    rxjs, bullmq
// npm: 2M+ packages available

Tinox Stdlib — Advantages

  • JSON, HTTP server, JWT, bcrypt without external dependencies
  • REST framework in the stdlib (annotation-based)
  • Cron, pubsub, cache out of the box
  • Stdlib written in Tinox itself (no paradigm switch)

Tinox Stdlib — Disadvantages

  • No database access (no JDBC/ORM equivalent)
  • No Spring/Ktor ecosystem
  • No central repository for third-party libraries
  • No multi-platform target like Kotlin

🛠️ Tooling & Development Environment

Java

# Build
mvn compile / gradle build

# Tests
mvn test / gradle test

# Format
google-java-format *.java

# LSP / IDE
IntelliJ IDEA, Eclipse, VS Code
JDT Language Server

# Deployment
java -jar app.jar
docker / GraalVM native-image

Kotlin

# Build (Kotlin DSL)
./gradlew build
./gradlew compileKotlin

# Tests
./gradlew test

# Format
ktlint, detekt

# LSP / IDE
IntelliJ IDEA (first class)
VS Code + Kotlin Language Server

# Deployment
java -jar app.jar
kotlinc → native binary (Kotlin/Native)

Tinox

# Everything in one CLI
tinox build program.tnx   # → a.out
tinox run   program.tnx
tinox check program.tnx
tinox fmt   program.tnx
tinox test                # unit tests (Rust-based)

# Package manager
tinox install             # from tinox.yaml
tinox add io.ex lib 1.0 https://...
tinox package             # → app-1.0.0.tar.gz

# LSP
tinox-lsp  (Eclipse plugin)

# Deployment
./a.out   (direct native binary)

TypeScript

# Compile
tsc / tsc --watch

# Run
node dist/main.js
ts-node src/main.ts
bun run src/main.ts

# Format / Lint
prettier --write src/
eslint src/

# Package manager
npm install / npm run build

# LSP
tsserver (VS Code, IntelliJ, Neovim)

# Deployment
node dist/ or Docker container

Tinox Tooling — Advantages

  • Everything in one CLI (build, run, check, fmt, install, package)
  • No build-tool configuration chaos
  • Native binary = no JRE deployment needed
  • Formatter is part of the language

Tinox Tooling — Disadvantages

  • No IntelliJ/VS Code plugin (Eclipse LSP only)
  • No debugger (GDB integration missing)
  • No REPL (planned)
  • No central package repository

📦 Package Manager

Declaring Dependencies

Java (Maven pom.xml)

<dependencies>
  <dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.10.1</version>
  </dependency>
</dependencies>

# Install
mvn install

Kotlin (build.gradle.kts)

dependencies {
    implementation("org.jetbrains.kotlinx:
        kotlinx-coroutines-core:1.7.3")
    implementation("io.ktor:ktor-server-core:2.3.5")
    testImplementation(kotlin("test"))
}

# Install
./gradlew build

Tinox (tinox.yaml)

package:
  name: myapp
  version: "1.0.0"

dependencies:
  - group: io.example
    artifactId: mylib
    version: "2.1.0"
    url: https://example.com/mylib-2.1.0.tar.gz

# Install
tinox install

TypeScript (package.json)

{
  "name": "myapp",
  "version": "1.0.0",
  "dependencies": {
    "express": "^4.18.2",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "typescript": "^5.3.0"
  }
}

# Install
npm install

Adding a Dependency & Packaging

Java (Maven)

# Add a dep (manually in pom.xml)
# or via Maven Wrapper
mvn dependency:get \
  -Dartifact=com.example:mylib:1.0.0

# Package
mvn package
# → target/myapp-1.0.0.jar

# Deploy to Maven Central
mvn deploy  # (GPG + Sonatype required)

Kotlin (Gradle)

# Add the dep in build.gradle.kts,
# then:
./gradlew build

# Package
./gradlew jar          # → build/libs/*.jar
./gradlew shadowJar    # fat jar (Shadow plugin)

# Kotlin/Native binary
./gradlew linkReleaseExecutableNative

# Publish to Maven Central
./gradlew publish

Tinox

# Add a dep directly via the CLI
tinox add io.example mylib 1.0.0 \
  https://example.com/mylib-1.0.0.tar.gz

# Package
tinox package
# → myapp-1.0.0.tar.gz

# Import in code
import mylib.MyClass

TypeScript (npm)

# Add a dep
npm install express
npm install --save-dev @types/express

# Package
npm run build    # tsc → dist/
npm pack         # → myapp-1.0.0.tgz

# Publish
npm publish

# Import
import express from "express";

Tinox Package Manager — Advantages

  • YAML instead of XML — noticeably more readable and compact
  • URL-based: no central repository needed (self-hosted)
  • Everything in the same CLI (no separate mvn/gradle)
  • tinox package produces a distributable tar.gz directly
  • Deps are project-local (.tinox/deps/) — no global cache conflicts

Tinox Package Manager — Disadvantages

  • No central repository (no tinox search)
  • No transitive dependency resolution
  • No checksum/signature verification (yet)
  • No version-conflict resolution

🌐 Web & REST APIs

Defining REST Endpoints

Java (Spring Boot)

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping
    public ResponseEntity<List<User>> list() {
        return ResponseEntity.ok(service.findAll());
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public User create(@RequestBody User u) {
        return service.save(u);
    }

    @GetMapping("/{id}")
    public User get(@PathVariable Long id) {
        return service.findById(id);
    }
}

Kotlin (Ktor)

fun Application.configureRouting() {
    routing {
        get("/users") {
            call.respond(userService.findAll())
        }
        post("/users") {
            val u = call.receive<User>()
            call.respond(HttpStatusCode.Created,
                userService.save(u))
        }
        get("/users/{id}") {
            val id = call.parameters["id"]
            call.respond(userService.findById(id!!))
        }
    }
}

Tinox

import tinox.core.http_server;
import tinox.core.json;

@JsonSerializable
class User
{
    var id: Int64;
    var name: String;
}

class UserController
{
    @GET
    @Path("/users")
    @Produces("application/json")
    @StatusCode(200)
    fn listUsers(ctx: HttpContext) -> Nothing
    {
        const users: User[] = [
            User { id: 1, name: "Alice" },
            User { id: 2, name: "Bob" }
        ];
        ctx.response.body = Json::serialize(users);
    }

    @POST
    @Path("/users")
    @Consumes("application/json")
    @StatusCode(201)
    fn createUser(ctx: HttpContext) -> Nothing
    {
        const user: User = Json::deserialize<User>(
            ctx.request.body
        );
        ctx.response.body = Json::serialize(user);
    }
}

TypeScript (Express)

import express, { Router } from "express";

const router = Router();

router.get("/users", (req, res) => {
    res.json([{ id: 1 }]);
});

router.post("/users", (req, res) => {
    res.status(201).json({ id: 2 });
});

router.get("/users/:id", (req, res) => {
    const { id } = req.params;
    res.json({ id });
});

Tinox Web — Advantages

  • Annotation syntax identical to Spring (easy to learn)
  • HTTP server directly in the stdlib — no framework download
  • Native binary starts instantly (no JVM warmup)
  • Path parameters, middleware, routing out of the box

Tinox Web — Disadvantages

  • No Spring/Ktor ecosystem (security, data, actuator…)
  • No ORM / database connectivity
  • No API documentation generator (Swagger/OpenAPI)
  • Less battle-tested for production workloads

🔐 Logging & Privacy

Java (SLF4J + Logback)

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// Logback PatternLayout with a masking filter
// or manual toString() override

public class User {
    private static final Logger log =
        LoggerFactory.getLogger(User.class);

    private String name;
    // Sensitive field: mask manually
    private String password;

    @Override
    public String toString() {
        return "User{name=" + name
            + ", password=***}";
    }

    public void doSomething() {
        // Explicit toString() needed, or
        // implement your own masking logic
        log.info("User: {}", this);
    }
}

Kotlin

import org.slf4j.LoggerFactory

class User(
    val name: String,
    val password: String  // no language support
) {
    private val log = LoggerFactory
        .getLogger(User::class.java)

    // Manual: override toString
    override fun toString() =
        "User(name=$name, password=***)"

    fun doSomething() {
        log.info("User: $this")
    }
}

// Or: data class with a custom toString
// No annotation support in the language
// → external libs (Jackson @JsonIgnore etc.)

Tinox

import tinox.core.logger;

@Log
class User
{
    var name: String;

    @Sensitive
    var password: String;   // → always "***"

    @Masked
    var email: String;      // → "al***ce" (2+2)

    fn doSomething() -> Nothing
    {
        // toString() is generated automatically
        // and honors @Sensitive/@Masked
        Logger::info(this.log, "User: " + this);
        // prints: User{name=Alice, password=***, email=al***ce}

        // Also for direct string expressions:
        const s: String = this.email;
        Logger::info(this.log, s); // unmasked value
        // Only the toString() conversion is masked
    }
}

TypeScript

// No native logger in Node.js
// Manual masking or external libs
import pino from "pino";

class User {
    name: string;
    password: string;
    email: string;

    constructor(name: string,
                password: string,
                email: string) {
        this.name = name;
        this.password = password;
        this.email = email;
    }

    toString() {
        const masked = this.email.slice(0, 2)
            + "***"
            + this.email.slice(-2);
        return `User{name=${this.name},`
            + ` password=***, email=${masked}}`;
    }
}
// No annotation support in JS/TS
// Decorators (@) are experimental / stage 3

Tinox Logging — Advantages

  • @Log automatically injects a log: Logger field — no boilerplate
  • @Sensitive and @Masked mask fields automatically in toString()
  • Masking applies to string concatenation ("User: " + this) with no explicit call
  • Privacy is part of the language — no external framework needed
  • No manual toString() override required

Tinox Logging — Disadvantages

  • No configurable masking pattern (length, characters)
  • No structured logging (JSON format) out of the box
  • No runtime log-level filter

🔒 Error Handling & Null Safety

Java

// Checked vs. unchecked exceptions
int divide(int a, int b)
    throws ArithmeticException {
    if (b == 0)
        throw new ArithmeticException("Div/0");
    return a / b;
}

try {
    int r = divide(10, 0);
} catch (ArithmeticException e) {
    System.err.println(e.getMessage());
} finally { cleanup(); }

// Null safety: missing
Optional<String> opt = Optional.ofNullable(s);
opt.ifPresent(System.out::println);

Kotlin

// No checked exceptions
fun divide(a: Int, b: Int): Int {
    if (b == 0) throw ArithmeticException("Div/0")
    return a / b
}

try {
    val r = divide(10, 0)
} catch (e: ArithmeticException) {
    println(e.message)
} finally { cleanup() }

// Null safety in the type system:
var s: String? = null
println(s?.length)   // safe call → null
println(s ?: "empty") // Elvis operator
s!!.length            // force unwrap (NPE possible)

Tinox

fn divide(a: Int64, b: Int64) -> Int64
{
    if b == 0
    {
        throw "Division by zero";
    }
    return a / b;
}

try
{
    const r = divide(10, 0);
}
catch e: RuntimeError
{
    println("Error: ${e}");
}
finally { cleanup(); }

// defer for automatic cleanup
fn readFile(path: String) -> String
{
    const f = open(path);
    defer { f.close(); }
    return f.read();
}

TypeScript

function divide(a: number, b: number): number {
    if (b === 0) throw new Error("Div/0");
    return a / b;
}

try {
    const r = divide(10, 0);
} catch (e) {
    // e is unknown — needs a type guard
    if (e instanceof Error)
        console.error(e.message);
} finally { cleanup(); }

// Null safety via strictNullChecks:
let s: string | null = null;
console.log(s?.length); // undefined

// Result pattern (neverthrow):
type Result<T, E> = Ok<T> | Err<E>;

Kotlin Error Handling — Advantages

  • Null safety built into the type system (T?)
  • Elvis operator (?:) for compact fallbacks
  • No checked exceptions (no throws chaos)
  • runCatching { } as a functional wrapper

Tinox Error Handling — Advantages

  • defer is more elegant than try-with-resources
  • No checked-exception chaos
  • Option<T> / Result<T> available in the stdlib
  • LIFO order of multiple defers is intuitive

🏆 Verdict & Recommendation

When Is Which the Better Choice?

Use Java when…

you need a large team, a huge ecosystem (Spring, Hibernate, JEE), or enterprise software with long-term support. Database integration, microservice orchestration (Kubernetes, Spring Cloud), and broad tooling (IntelliJ, APM) are decisive.

Use Kotlin when…

you want the JVM platform but value modern language features: null safety, coroutines, data classes, extension functions. Ideal for Android development, server-side with Ktor, and teams migrating from Java. Full Java interop included.

Use Tinox when…

you want native performance without JVM overhead, are building small to medium services or CLI tools, or value Java-like syntax with less boilerplate. Ideal for instant-startup scenarios, in-house development, and learning projects.

Use TypeScript when…

you're building web apps, APIs, or full-stack projects with the huge npm ecosystem. Frontend and backend share the same language, IDE support is excellent, and the team has a JavaScript background.

Tinox is a modern language under active development — package manager, LSP, REST framework, and 50+ stdlib modules are available. The compiler (lexer, parser, typechecker, codegen) is covered by ~980 unit tests. Further features (debugger, REPL, central repository) will be added in future versions.