⚖️ Quick Comparison
📝 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
@Logautomatically injects alog: Loggerfield — no boilerplate@Sensitiveand@Maskedmask fields automatically intoString()- 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?
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.
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.
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.
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.