TS
JV

TypeScript to Java

10 lessons

Progress0%
1Introduction: Compiler to JVM2Type Systems: Structural vs Nominal3Classes & OOP4Generics5Modules to Packages6Null Safety7Async to Threads8Ecosystem9Exception Handling10Collections and Stream API
All Mirror Courses
TS
JV
Type Systems: Structural vs Nominal
MirrorLesson 2 of 10
Lesson 2

Type Systems: Structural vs Nominal

Type Systems

Introduction

In this lesson, you'll learn about type systems: structural vs nominal in Java. Coming from TypeScript, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.

Mirror Card
TS
From TypeScript:

In TypeScript, you're familiar with type systems.

JV
In Java:

Java has its own approach to type systems, which we'll explore step by step.

The Java Way

Let's see how Java handles this concept. Here's a typical example:

JV
Java Example
// Java uses NOMINAL typing
interface Printable {
    void print();
}

// Must EXPLICITLY declare that a class implements the interface
class Document implements Printable {
    @Override
    public void print() {
        System.out.println("printing...");
    }
}

public class Main {
    static void output(Printable item) {
        item.print();
    }

    public static void main(String[] args) {
        output(new Document()); // Explicit implementation required
    }
}

Comparing to TypeScript

Here's how you might have written similar code in TypeScript:

TS
TypeScript (What you know)
// TypeScript uses STRUCTURAL typing
interface Printable {
  print(): void;
}

// Any object with a print() method satisfies Printable
const doc = {
  print() { console.log("printing..."); }
};

function output(item: Printable) {
  item.print();
}

output(doc); // Works — shape matches, no explicit declaration needed
Mirror Card
TS
From TypeScript:

You may be used to different syntax or behavior.

JV
In Java:

TypeScript: structural — if the shape matches, the type matches

Mirror Card
TS
From TypeScript:

You may be used to different syntax or behavior.

JV
In Java:

Java: nominal — must explicitly declare implements InterfaceName

Mirror Card
TS
From TypeScript:

You may be used to different syntax or behavior.

JV
In Java:

Java interfaces are contracts enforced at the class declaration level

Mirror Card
TS
From TypeScript:

You may be used to different syntax or behavior.

JV
In Java:

No 'duck typing' in Java — the compiler checks the declaration, not just the shape

Step-by-Step Breakdown

1. Structural vs Nominal — The Core Difference

In TypeScript, a value satisfies a type if it has the right shape. In Java, a class must explicitly declare which interfaces it implements.

TS
TypeScript
interface Flyable { fly(): void; }
const obj = { fly() {} };
const f: Flyable = obj; // OK in TS
JV
Java
// Java — must be explicit:
class Bird implements Flyable {
    public void fly() {}
}
Common Pitfall
You cannot pass an anonymous object literal as an interface type in Java. Use an anonymous class or a lambda (for functional interfaces).

2. Functional Interfaces and Lambdas

Java interfaces with a single abstract method (SAM) can be implemented with a lambda — this is the closest Java gets to structural typing.

TS
TypeScript
const onClick: () => void = () => console.log("click");
JV
Java
// Runnable is a @FunctionalInterface
Runnable onClick = () -> System.out.println("click");

3. Generics Are Nominal Too

Java generics are checked nominally. List<String> and List<Integer> are distinct types, and you cannot assign one to the other without wildcards.

TS
TypeScript
function first<T>(arr: T[]): T { return arr[0]; }
JV
Java
public static <T> T first(List<T> list) {
    return list.get(0);
}

4. @Override Annotation

@Override tells the compiler you are implementing or overriding an existing method. It is optional but strongly recommended — the compiler will error if the method signature doesn't match.

TS
TypeScript
// TypeScript just matches the method name and signature
JV
Java
@Override
public void print() { ... }
Rule of Thumb
Always add @Override when implementing interface methods or overriding parent class methods.

Common Mistakes

When coming from TypeScript, developers often make these mistakes:

  • TypeScript: structural — if the shape matches, the type matches
  • Java: nominal — must explicitly declare implements InterfaceName
  • Java interfaces are contracts enforced at the class declaration level
Common Pitfall
Don't assume Java works exactly like TypeScript. While the concepts may be similar, the syntax and behavior can differ significantly.

Key Takeaways

  • TypeScript is structurally typed — shape determines compatibility
  • Java is nominally typed — classes must explicitly declare implements
  • Functional interfaces allow lambda syntax for single-method interfaces
  • @Override annotation is best practice for all interface implementations
Rule of Thumb
The best way to learn is by doing. Try rewriting some of your TypeScript code in Java to practice these concepts.
PreviousNext