We switched from Java to Go and don't regret it
I always told people memory is cheap, black magic is OK and efficiency doesn't matter in most cases, but boy, how wrong was I...
My Java journey started back in 2011 (14 years ago - wow) when I started studying computer science at the Vienna University of Technology. Using editors like jEdit and compiling my Java programs by hand with the command line. My first "major" applications were Java Swing GUI applications and "old school" web applications with JSP and Servlets.
Professionally, I started writing Java code in 2016 when I joined a company that was developing a Java-based web application. It used the classic stack: Java, Spring Boot, Hibernate, and a PostgreSQL database. I always loved all the Spring features like Dependency Injection, Spring Security, and Spring Data JPA. Yes, the application took half a minute to boot up and consumed hundreds of megabytes of RAM instantly, but who cares? Memory is cheap, right?
Over the next few years, I always picked Java (and later on Kotlin) for new projects due to the familiarity and the vast ecosystem, and being afraid of slower development speed in other languages like Go or Rust due to the initial learning effort. We even went so far as writing infrastructure code for Kubernetes clusters that automatically provision apps in Kotlin. Although it is still used in production, I wish we had written it in Go from the beginning.
In hindsight, this decision seems to be obvious, but having already invested so much time and experience, the decision wasn't so clear back then. As a rule of thumb to ensure productivity and efficiency, I like to follow this credo.
Either do a project in an unfamiliar domain or with an unfamiliar tech stack.
As this was our first project in the Kubernetes domain, we didn't want to also introduce execution risk in addition to market/product risk.
But it became pretty clear early on why most of the current tooling is written in Go. Other operators barely consumed any memory or CPU. Our operator, with additional tooling, and our stack consumes way over 2 GBs of RAM even when idle.
In 2024, we decided to rewrite our first Kotlin-based "package operator" into a more generic and extensible package manager. Being already familiar with the domain, the Kubernetes API, and the operator pattern, we chose Go as the programming language.
After an actually not-so-steep learning curve, we were able to accelerate faster and faster and nearly match the development speed we would have had writing Kotlin.
As a company that is constantly evolving and launching new tools in the DevTool space, we decided in late 2024 to launch Distr – An Open Source Software Distribution platform, helping software vendors deploy into customer-controlled environments.
As we had familiarized ourselves with the Go stack and had experience in the software distribution domain, we decided to choose Golang for Distr, which will be our first web application in Go.
The first couple of times I started the web server, I waited a few seconds until it would "boot up" because I didn't see any new logs, but I realized it was already running and ready to accept connections.
Compile & Startup Time
Compilation
Java's JIT (Just-In-Time) compiler and Go's AOT (Ahead-Of-Time) compiler are definitely two very different approaches and therefore hard to compare. Where Java supports incremental builds hot code reloading which also allow for faster startup times in development. But this comes at a cost of using Gradle / Maven which definitely have an appetite for memory themselves and are sadly still far from providing an excellent developer experience.
Go, on the other hand, compiles the whole application into a single binary, which requires a full rebuild on every change.
Startup Time
Starting a real-world but still super light Spring Boot application outputs round about the following:
...Started CoreApplicationKt in 8.201 seconds (process running for 8.726)
Where the Go web server is ready to accept connections in less than 100 milliseconds.
In the course of a developer's years, if I only restart the server two times an hour, this saves me an additional day (!) of development time per year.
This can also get critical in a scenario where all replicas are scaled down to zero and need to be started up again to accept requests as quickly as possible.
I mean, who doesn't like speed? I sure do. 😎
Frameworks and Libraries
In the Java ecosystem, most frameworks exist covering a whole part of the stack from the web server to the database layer.
Go is different, having smaller libraries, for example, web server, router, database, which don't necessarily belong together but play well together, giving you the ability to pick the libraries you like instead of using an all-in-one solution like Spring or Quarkus. But with the landscape being more fragmented, you will see projects differ more than your traditional Java EE or Spring Boot application.
Dependency Injection & Context
Coming from Spring Framework, I was used to annotating my services with @Service
, and I could happily use them in
other services. This is also how Angular works. But Go is different.
Although singleton services even exist in the standard library e.g. http.DefaultClient
or base64.StdEncoding
dependency
injection is not a real thing as runtime reflections is much more limited in Go when compared to Java.
But there are obviously work around solutions in the Go ecosystem.
It uses the Context ctx
, which we pass around functions in order to juggle data around in the application.
Having less black magic around dependency injection is definitely a general theme in Go and probably also a main reason for its popularity.
Debugging & IDE Support
In the beginning, I was very skeptical about the debugging capabilities of Go-based applications, but debugging applications, setting breakpoints, and evaluating expressions is as convenient as with applications that run in the JVM.
So, IDE support is definitely comparable.
But when it comes to exceptions and stack traces, I've had better experiences with the Java ecosystem. I personally think that IDE support (at least in IntelliJ) is slightly superior, as it hides traces from frameworks and creates hyperlinks to the source files inside the scope, where you can quickly browse into them. I haven't seen IntelliJ doing this with Go.
Debugging and IDE support, which are crucial for developer experience, are mostly on par, but from my experiences, stack traces being more tightly coupled to your sources in the Java ecosystem are better.
CI & Release Tooling
Releasing is always related to dependency management and application bundling. In the Java world, there are Gradle and Maven, with Maven being the established player in the field and Gradle the "newer" version. Although the Java ecosystem is already that old, there are basically no new tools around.
In the Go ecosystem, I think that there are fewer libraries that do the same thing. There is often one good solution for dependency management, and GoReleaser is a great tool.
Summary
Looking back, switching from Java/Kotlin to Go felt like a big step at first, but in hindsight, it was the obvious choice. The initial learning curve wasn’t as steep as expected, and the benefits—blazing-fast startup times, lower resource consumption, and a more lightweight ecosystem—became apparent pretty quickly.
Of course, Java still has its strengths, and for certain projects, it remains a solid choice. But for cloud-native applications, Kubernetes tooling, and our self-hostable software distribution platform, Go just feels like the right tool for the job.
Will I ever write Java again? Probably. But for now, I’m enjoying the speed, simplicity, and flexibility of Go.
And honestly, I don’t miss all of Java's and Spring's black magic under the hood. 😄