Java, is it that bad?

At university, I took a course called “Interactive Application Programming” where I started using Java to learn how to make desktop applications that would run anywhere (thanks to the multiplatform properties of Java).

Back then, more experienced programmers were already mentioning that Java was too verbose. To be honest, I also found it too much descriptive compared to what I had previously used (c++ and Ruby), as it required to “over-specify” everything.

// For instance, you had to specify the type of the list on both ends instead of assuming it at compilation time, which gave something like this:
List<String> list = new ArrayList<String>();

// Instead of having this now:
var list = new ArrayList();

But I have been using Java for many years in my career and would like to use this article to show that it’s not as bad as people say (and that you can change your mind ;) )

What is Java?

First of all, let’s introduce Java. Java is a programming language that was created by James Gosling back in 1995. It was created to be a general-purpose programming language that could run anywhere. It is a statically typed high-level object-oriented language.

Thanks to the JVM (Java Virtual Machine) Java can run on any platform that has a JVM installed. This means that it’s not a “compiled” language unlike c++ but interpreted like Ruby.

Java was designed with the following primary goals in mind Reference:

  • It must be simple, object-oriented, and familiar.
  • It must be robust and secure.
  • It must be architecture-neutral and portable.
  • It must execute with high performance.
  • It must be interpreted, threaded, and dynamic.

In other words, Java seems to be the super Swiss-knife of programming languages (you know, the ones with a tiny magnifier, a bottle opener, multiple knives…)

Now that we understand what Java is and how it was designed let’s talk about “its issues”.

Versions

Since Java is (when I write this article) 27 years old, it has gone through many versions and it may be difficult to keep up.

The first version I used was Java 6 but that’s not the one I’m going to talk about since it would not be beneficial to anyone, and it would be too time-consuming to write about it.

In this article, I will expand on Java 17, which is the latest current LTS (Long Term Support) version. (Most companies I’ve worked for usually stick to LTS versions and only upgrade when there’s another one released).

Hello World

One of the things some people like to use is how much it takes a language to do a “hello world”, as a scale to assess how verbose a language is.

package org.example;

public class Main {

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

Currently, you can write it like above and, although is not as simple as Ruby, it is fairly similar to other languages like Go or C#.

puts "Hello World"

The “issue” with Java’s hello world is that it needs to meet a minimum set of requirements

public static void main(String[] args)

Every java application entry point needs to have this as it’s the only way to run the whole application. In interpreted languages like ruby or python, you can run every single file independently. Fun fact this is what the application does when you do a require: it will run the whole file. That’s why you see in most python files the famous:

if __name__ == '__main__':
    print('Hello World')

It ensures that the code is not run but is loaded when you require it and instead only runs when you call the file directly.

Classes + Interfaces

Another aspect of Java people don’t like about java is having a single file per class/interface. Although this is not a requirement, it’s an agreed principle and way of working. This is coming from the c++ inheritance where you would specify the API of your class (interface) on the header file and the implementation (class) on another file, allowing you to have multiple implementations for the same interface and being able to read the one you’re interested in.

You can ignore this standard and have multiple class files on the same file, but it means that they can’t be public. So they can only be used within the same package, clearly saying to other people this is something only I should be using. In the example below if you tried to use Animal or Cat from another package it would not compile.

package org.example;

interface Animal {
    String sayHi();
}

class Cat implements Animal {

    @Override
    public String sayHi() {
        return "Miau";
    }
}

So is this bad or is it just about describing to the reader the access model of such files? Other languages, like go, do similar things by using the case of the class/interface.

package foo

type app struct {
name string
}

The struct (“class” in golang) above can only be used within the package ‘foo’, and if you wanted to export it to other packages, you would need to change the case of the struct from app to App.

Why Interfaces?

In Java, we use interfaces a lot as it’s what we use to specify clear contracts between different modules: very early in java, it’s been agreed that you should not use an implementation (ArrayList or Dog) and instead you should always use the general concept, the interface (List or Animal)

That way, you can have your code following SOLID! principles, allowing you to easily inject the dependency you need,


interface ExternalApi {
    String getValue();
}

class InMemoryExternalApiClient implements ExternalApi {

    public InMemoryExternalApiClient(inMemoryValue){
      this.inMemoryValue = inMemoryValue;
    }

    @Override
    public String getValue() {
        return "inMemoryValue";
    }
}

class RealApiClient implements ExternalApi {

    public RealApiClient(dependency){
      this.dependency = dependency;
    }

    @Override
    public String getValue() {
        return dependency.getValue();
    }
}

I guess I don’t have to sell you the benefits of dependency injection, but I hope it makes you understand the reasoning behind the interfaces.

Data Access

The biggest complaint people have on java is that you need to write a lot of boilerplate code to be able to access a class internal value, or private, property.

class Dog implements Animal {

    private Integer age;
    private String name;

    public Dog(Integer age, String name) {
        this.age = age;
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String sayHi() {
        // Yes, dogs say Guay in Spanish
        return "Guay";
    }
}

Here I completely agree with people about the amount of code that needs to be written to be able to have such a simple class where you don’t care about how the value gets shown to the user (i.e you’re just returning the value it contains). In ruby, it’s a lot simpler to have a default getter

class Dog < Animal

  attr_reader age, name

  def initialize(age, name)
    @age = age
    @name = name
  end

  def say_hi
    # Yes, even with ruby, dogs say Guay in Spanish :wink:
    "Guay"
  end
end

This is why currently Java has options when you want to have a simple data carrier (POJO) to use record classes. They can’t implement interfaces or inherit, but they are quite a nice tool when you just want to have something to carry some data representing something i.e a Turtle object with its name and age.

record Turtle(Integer age, String name) {
}

These record classes are quite similar to golang’s struct, albeit way less powerful as a struct is more like a class in java. Or data classes in python.

import dataclasses


@dataclasses.dataclass
class Turtle:
    name: str
    age: int

Frameworks

I believe what people dislike quite a bit is the different frameworks (specially SpringBoot), trying to do a lot for the people, but in the end confusing them and not letting them understand what’s going on (often happening because they lack experience in the java world or because they are new to programming). Though, this applies to many frameworks like Django, Flask, Rails, etc. There’s currently a trend in which people create much simpler frameworks meant to be used in a different way, letting people understand and decide what to do. Some examples are Dropwizard, Micronaut (although this has quite a bit of magic made for you) for java, or FastApi and Hanami for python and ruby.

IDE

I think the reason most people don’t like java is that you really need an IDE that helps you do things at a fast pace. For example with IntelliJ by Jetbrains (which is my favourite IDE for any language) you can simply write sout and it will replace it all with System.out.println(); and many other templates that you could add ( for instance, a template I used to have was to write a test).

IDEs are also very useful to auto-generate the getters or setters, or make sure my class implements or overrides th interface/class the way it’s meant to be. The IDE would tell me that I’m doing something wrong and will help me do it right.

You can do the same with other editors such as vim, though you should use NeoVim, or Visual Studio Code. You can code with java without a java specialized IDE, but you will need to configure it to do what you want the way you want.

Conclusions

You probably have a guess at what my opinion on Java is by the way I wrote the article. I think there’s no bad or good language. They are all different tools with a different context in mind that influenced the way they were designed, and the same applies to Java. I encourage you to explore more on the topic of language design. For example the way Steve Francia explains how golang was designed and the reasoning behind every decision they took is quite interesting. This is why I started trying to give you a bit of context of how java came to be and why the things that people complain about are all very valid points.

If you’ve read my article about Reflections about Clean Code you should start to understand that the way to enjoy working on a project is not by choosing the best language but by making the decisions that make sense to you and your coding principles. This is why for example I don’t enjoy frameworks like SpringBoot, Rails or Django. There are many decisions that are taken from me that do not match my principles (especially with Rails).

Do I expect you to fall in love with Java and use it everywhere? Nope, there are different reasons for choosing Java or other languages. Oftentimes, it’s related to the number of people in the market working with the language. Other times, it’s the type of application you want to design that will influence your choice of language (For instance, if you want to create an MVC where you have a monolith because you are doing something that requires everything on the same server and being able to access things in memory rather than over the internet I believe ruby with Hanami is a very strong suit for it, if not the best). But it’s rarely the verbosity of the language on its own… as it’s just a tool :)

Thanks

I’d like to thank my wife Marie Dziubich for helping me review the post and my friend Rubén Ángel Negrin Afonso with whom I’ve discussed this from his point of view as someone who never wrote Java (professionally speaking).