Comparing TypeScript to Java

Posted on Posted in Concepts of programming languages, TypeScript

To my surprise, I saw that many people visit my website after searching for “TypeScript vs. Java”. The question makes sense, so let’s answer it. What’s the difference between the two languages? When to chose which? Do they compete at all?

Why should we compare TypeScript to Java?

At first glance, the answer is simple. TypeScript “lives” in the browser. Java lives everywhere else. So they don’t compete at all. End of the article.

However, TypeScript is an attractive language for people with a Java background. To them, the question is slightly different. Is TypeScript an easy language to learn?

Plus, there’s Node.js. Using JavaScript on the server has become surprisingly popular. TypeScript compiles to JavaScript, so it has become a direct competitor to Java.

TypeScript for the hurried Java developer

Traditionally, most Java programmers hate JavaScript. TypeScript changes all that. You’ll love TypeScript if you enjoy programming Java.

That’s because TypeScript adds a little syntactic sugar to JavaScript. It’s not much. Each time I compare the TypeScript source code to the JavaScript code generated by the transpiler, I’m surprised how little differences there are. You can see this yourself by visiting the TypeScript playground.

Basically, TypeScript adds a few concepts to JavaScript:

  • classes
  • private attributes
  • closures (with fat arrow syntax and intuitive variable scope)
  • decorators (roughly comparable to Java’s annotations)
  • getters and setters

None of these concepts are new to JavaScript. They are emulated using functions. But that’s the catch: JavaScript uses a single idea – functions – to implement a variety of different concepts. TypeScript uses dedicated keywords. This makes TypeScript code a pleasant read. As a side effect, Java programmers feel at home in TypeScript country in no time. You don’t loose anything by embracing TypeScript.

Advantages of TypeScript over Java

TypeScript benefits from its powerful type inference. This makes for a fairly relaxed programming style unless you exaggerate it. Java forces you to declare trivial facts over and over again. It doesn’t suffice to know that 42 is an integer number, you have to jot it down:

int fourtyTwo = 42;

TypeScript allows you to omit the data type:

let fourtyTwo = 42;

In this particular case, that’s not very exciting. We’ve replaced a three-letter keyword by another three-letter keyword. Things get interesting with advanced types, such as arrays and maps. Since Java 7, we’ve got a limited type inference called target typing. Even so, Java is a bit ceremonious:

// Java 7 and above:
Map<String, String> trafficLights = new HashMap<>();

// Java 6 and below:
Map<String, String> trafficLights = new HashMap<String, String>();

TypeScript does it a bit simpler:

let trafficLights = new Map<string, string>();

Not bad, but in many cases, we can do even better. Type inference becomes really nice if combined with the array and map literals of TypeScript:

let colors = ["red", "yellow", "green"]
// this is an Array<string>

let trafficLights = ["red"   : "stop", 
                     "yellow": "be careful", 
                     "green" : "go"];
// this is a new Map<string, string>

Pitfalls

Funny thing is that type inference is the cause of a common mistake during the first couple of days. Java always puts the type before the name of the variable. TypeScript allows you to optionally define the data type, but it puts the type behind the variable name. For instance, method definitions are sort of the other way round:

// Java
public int multiply(int a, int b) {
   return a * b;
}
// TypeScript
public multiply(a: number, b: number): number {
   return a * b;
}

Advanced type inference

Type inference also applies to methods, much the way it does in Scala. If you don’t specify the return type, the compiler checks every return value and determines the common data type:

public multiply(a: number, b: number) {
  return a * b;
}

public consumer() {
  // tooltip shows: "let numeric: number = ..."
  let numeric = this.multiply(4, 2);

  // doesn't compile
  // tooltip shows: "Type 'number' is not assignable to 'string'"
  let text: string = this.multiply(4, 2);
}

I consider this feature a mixed blessing. In general, I recommend specifying the data type of both the method parameters and the return value. It makes other developers’ lives easier when they are using your method. On the other hand, if you’ve good a decent IDE, you can also check the tooltip to learn about the data type.

TypeScript’s powerful type system

The name of the language nails it: TypeScript is all about adding types to JavaScript. So it’s no surprise that the type system of TypeScript has more features than the type system of Java. But most of the time you won’t see much of a difference. Simplifying things a bit, the type system of TypeScript is a superset of Java’s type system (from a programmer’s perspective). Once you inspect the type systems more closely, you’ll discover a lot of differences, but this article is not a scientific essay.

Most differences stem from the JavaScript heritage. There’s only one numeric type. Everything’s a number, no matter if it’s a simple integer or a full-blown floating point number. The JIT compiler of most modern browsers uses integer and floating point types internally. But that’s only a performance optimization. The language doesn’t force you to think about the exact data type. Most programmers don’t mind, but that’s one of the features making for a more relaxed programming style.

There’s also the (in)famous any type. This type converts the strong type system into an optional strong type system. If you’re using any, you’re using the weak, dynamic type system of JavaScript. As the name implies, any can be anything: a number, a string, an array, an option or a closure. In general, that’s not a good idea, especially in the light of the decent type inference which makes using strong types a breeze. However, any is necessary to include third-party JavaScript libraries.

Interfaces and type definition files

The drawback of using any is that autocompletion is broken. If your IDE doesn’t know anything about the data type, it can’t suggest you any method or attribute name. TypeScript solves this problem by making interfaces incredibly flexible. You can assign any object to an interface if the structure of the object matches the interface. The interface defines which attributes must be present. If the object has these attributes, there’s nothing stopping the type case:

interface Person {
    lastName: string;
    firstName: string;
    birthDate?: Date;
}

let john: Person = {
    lastName: "John",
    firstName: "Doe",
    birthDate: new Date(1970, 2, 28)
}

let jane: Person = {
    lastName: "Jane",
    firstName: "Doe"
}

This example also shows two other nice tricks of TypeScript. Some attributes of an interface can be optional, and the language has decent built-in Json support. That’s important because many applications receive data from the server using REST calls. The data is simply a string. Json support and structural compatibility to interfaces make it easy to convert these strings to useful entities with strict compiler checks and the autocomplete suggestion of your IDE.

The flexible interfaces make type definition files possible. Most popular JavaScript libraries have a TypeScript type definition hosted on a repository, such as DefinitelyTyped. Libraries such as jQuery and D3.js benefit from type definitions by simply adding a single line to the package.json of the project.

Development infrastructure

Talking of the package.json: Java developers have to get familiar with a new tool chain when they start using TypeScript. This can be overwhelming. Every developer seems to have their favorite tools. I don’t want to explain the tools in depth here, so suffice it to provide a small table:

Topic Java tool TypeScript tool
Software development kit (SDK) JDK Node.js + tsc
Compiler javac tsc
Package manager Maven or Gradle npm
Dependency definition file pom.xml package.json
IDE Netbeans, IntelliJ, Eclipse Sublime, Atom, Webstorm, IntelliJ, Eclipse, etc.
static code analysis Sonar, Findbugs, Checkstyle, PMD, etc. tslint
Compiler configuration n/a (apart from VM parameters) tsconfig.json

Automatic null check

A compiler option you should activate is the automatic null check. It’s available since TypeScript 2.1 and uses the union types of TypeScript. After activating the null checks, you can’t assign null to a variable. Every object is expected to have a proper value. If you really need null values, you have to define that explicitly:

// this causes an error:
let error: string = null;

let correct: string | null = null;

This eliminates the nasty errors caused by unexpected null values. Unfortunately, you don’t get rid of the famous error message “undefined is not a function”. It’s a peculiarity of the JavaScript language that you can access variable before defining them. TypeScript inherits this treat, and there doesn’t seem to be anything to do about it.

Collections API

The collection API of TypeScript is roughly comparable to the collection API of JavaScript, but it’s a lot more useful because you don’t have to deal with streams. The Java 8 approach is a bit cumbersome:

List<String> colors = new ArrayList<String>() {{
                      add("red");
                      add("yellow");
                      add("green");
}};

List capitalColors = colors.stream()
                     .map(color -> color.toUpperCase())
                     .toArray(String::new);

The TypeScript counterpart is a bit more to the point:

let colors = ['red', 'yellow', 'green'];

let capitalColors = colors.map(color => color.toUpperCase());                     
Side-by-side comparison

Topic Java TypeScript
Ecosystem
(IDEs + tool chain)
++ +
Maturity ++ ++
programming style precise, ceremonial concise, relaxed
functional programming 0 (lambdas, streams) ++ (closures, collections API)
type inference 0 (target typing) ++
type system static + strong strong, optionally dynamic
null-safe programming limited IDE support optional support by the language
debugging ++ + (due to JavaScript heritage)
Browser via GWT or TeaVM native
Desktop JavaFX, SWT Electron
Server JavaEE, Spring Node.js (single-threaded, cooperative multi-tasking)
Performance ++ + (depending on the browser)

Disadvantages of TypeScript

Of course, there are also advantages of Java over TypeScript. Probably the most important feature is the reflection API. Most popular libraries use the reflection API to make the Java developer’s lives easier. Just think of Spring (e.g. @Autowire / @Inject, CDI (@Inject), Hibernate and JPA (@Entity and the optional @Column, EJB, and so any. A lot of code relies on annotations and a framework evaluating these annotations. In most cases, this framework uses the reflection API to find the annotated classes, methods and attribute.

TypeScript library developers can achieve similar things because the decorators of TypeScript are not only annotations but real code that’s executed. However, few developers use this feature (yet). For instance, Angular adds one or more classes to each project just to declare which classes are used. That’s pretty much the Spring configuration file we’ve used (and hated) in earlier Java versions.

When to use which?

Most of the time, the answer is simple: use TypeScript for the browser and Java for everything else.

Node.js allows you to use TypeScript on the server and the desktop, too. If you’ve got experienced JavaScript or TypeScript programmers, that’s a good option. Node.js is a decent web server, and Electron opens the world of desktop programming to you. However, if your team consists of proficient Java programmers, stick to Java. The tool chain and JavaEE currently are more mature than their TypeScript counterparts. TypeScript and JavaScript are catching up quickly, so my answer might be different next year. Mind you: the programming model of a Node.js server is simpler than the programming model of a JavaEE application, which may be a plus with respect to maintenance.

Tools like TeaVM and GWT allow you to use Java in the browser, too. However, I don’t think that’s a good option. It might be an option if the alternative language is JavaScript. But if you’re choosing between TypeScript and Java, opt for TypeScript as a browser language. The extra effort to learn the language isn’t big enough to justify using a Java-to-JavaScript cross-compiler.

Wrapping it up

If you’re a Java programmer exploring TypeScript, you’ll love it. You loose a few features, but as an application developer, you won’t notice because the library developers have found other solutions. However, if you’re migrating from TypeScript to JavaScript, you’ll probably be disappointed. Java is very ceremonial. Often you end up with a lot of code that’d be a single line in TypeScript. However, the ecosystem of Java is much more mature. Plus, the language is faster, and it’s closer to the operating system. Node.js allows you to write TypeScript programs on the server, but it’s unlikely you’ll ever be able to use multi-threading. In general, I tend to use Java on the server side and TypeScript on the client side.

Dig deeper

Why Java developers will embrace Angular and TypeScript
Side-by-side comparison of Java and TypeScrip

Leave a Reply

Your email address will not be published.