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: MapTypeScript does it a bit simpler:
let trafficLights = new MapNot 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 ArrayPitfalls
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:
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 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:
ListThe TypeScript counterpart is a bit more to the point:
let colors = ['red', 'yellow', 'green']; let capitalColors = colors.map(color => color.toUpperCase());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.
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) |
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.