25.05.2025
16.05.2024
15.04.2024
04.04.2024
31.03.2024
29.03.2024
My coding journey began with BASIC on an IBM 286 PC, then spiraled into Assembly, C, C++, Pascal, and Delphi during school and university. My professional career began with PHP in 2009, followed by a brief romance with Ruby on Rails, and then—Java.
From 2011 to mid-2024, I lived and breathed Java almost exclusively.
Sure, I dabbled in JavaScript (an occupational hazard for any web developer),
and played with Scala, Elixir, Clojure, and Rust, to name a few.
But I always came back to Java.
I love Java.
The language is amazing, and its ecosystem is simply the best there is.
Especially Spring — I attribute most of Java's success to it,
there's nothing even remotely close.
I consider myself a Java guy.
Hell, I’m even a certified Oracle Java Professional Programmer, FFS.
My disappointment is immeasurable, and my day is ruined (c) TheReportOfTheWeek
But after 13 years in a committed relationship, I started noticing the red flags.
I can count on one hand the Java codebases I'd call truly "clean" —
and I might still have fingers left over.
Why?
Have I only worked with "bad" programmers?
Absolutely not.
Do I have unrealistic standards?
I don't think so.
At some point, it started to feel impossible.
And then I caught an intrusive thought: the biggest problem with Java is... drumroll... the people.
We are different, we've lived through different experiences, and we see things differently.
And Java plays very strongly to those differences.
It provides too many options, too many ways to do the same thing, and too many paradigms all over the place.
Especially now, when the release cadence is every six months, code written 3–5 years ago looks very much dated.
And code written in Java 6 (God bless) isn’t even considered Java anymore.
It’s paleontology.
Picture this: You join a new team. One developer has built a reactive streams pipeline that looks like it belongs in a Haskell textbook. Another has crafted a classical inheritance hierarchy so deep it makes your IDE scroll bar weep. A third has written what's essentially C with classes — static methods everywhere, zero abstraction. All three claim they're writing "good Java." All three are technically correct. And that's the problem.
That’s not flexibility — that’s chaos.
Mid 2024, life threw me a curveball disguised as a job offer.
I got hired as a Staff Engineer by a company that doesn't use much Java but primarily uses Go as a go-to language (yes, the pun is unavoidable, deal with it). By the time I signed the final offer, I had little to no idea what Go was. "Oh well, it's something from Google, and Kubernetes and Docker are written in it." During the interview process, I was told: "Don't worry, we hire a lot of Java people without Go background, you'll be fine." If you say so.
It's been almost a year at the time of writing this, and oh my God, I'm more than fine. I don't remember the last time I had so much fun programming on the weekend by myself. It feels so natural, fluent, and addictive. I'm constantly in the process of building something, be it a small AI-integrated CLI tool, micro AWS Lambda for a Telegram bot, or a backend that powers this blog.
The more I think about it, the more I realize — Google engineers who created GO are geniuses.
They made a perfect language for an average Joe.
The language follows a brutally simple motto: Less is more.
In fact, Go is the only language that consistently hosts conferences where people discuss features that should be
REMOVED from the language.
REMOVED.
Three ways to declare a variable?
Too many.
Leave one.
Of course, it's not happening because it will break backwards compatibility,
but this is the narrative that drives the community.
And it works.
After half a year of writing and reading Go daily, I opened the source code of Kubernetes — which is more than a decade old — and... understood everything. It's not an ancient manuscript to me. It's the same code I'm writing daily. Sure, the structure of the project and some approaches may differ, but the code is the same:
And one more—everything is formatted the same.
And don't get me started on the formatting wars...
Nothing I hate more than pointless styleguide discussions.
All those hours and energy wasted debating tabs vs spaces, line length limits, bracket positions, and chain-method alignments...
Ever hit "Reformat" on legacy code and Git blamed you for the whole mess? “Who wrote this crap?” “Apparently... Dave. Last Tuesday.” All because he hit Ctrl+Alt+L.
They played us for absolute fools.
Go just skipped the whole charade from day one withgofmt
.
Gofmt's style is no one's favorite, yet gofmt is everyone's favorite.
This is the way.
If this is not a success - I don't know what is.
For comparison, I tried to look at the source code of Apache Kafka once, and I couldn't understand much. "What is this piece of code doing? Something, I guess." — It looked nothing like the Java I've written for over a decade.
Another time, I wanted to fix a bug in an open-source openapi-diff
library.
I don't know what those guys were on,
but this stuff is considered too hardcore even by the Berlin industrial techno scene.
To me, it made absolutely no sense.
Just a bunch of eccentric abstractions.
The source code of Kafka at least made some sense.
Let me give you a quick taste of Go.
I'm not going to explain how to write for-loop or an if-statement.
You can figure it out by yourself.
Instead, I'll show you how to build a HelloWorld library and use it in another project.
It takes minutes, and it's freaking awesome.
Preconditions:
I’m not going to waste time walking you through environment setup — there are plenty of resources online for that.
Let's jump straight to the point.
mkdir go-hello-world-lib
cd go-hello-world-lib
go mod init github.com/{your_repo}/go-hello-world-lib
I hope the first two lines are self-explanatory.
go mod init github.com/{your_repo}/go-hello-world-lib
initializes go module - a single go.mod
file that
holds information about the module, such as its name, Go version, dependencies, etc.
In my case, the module name is github.com/mfenderov/go-hello-world-lib
-
technically, it could be whatever I want,
but it's a convention to name a module using a git repo URL,
I'm going to use GitHub in this case because I'm going for a fancy-pants open-source library, of course.
Content of go.mod
:
module github.com/mfenderov/go-hello-world-lib
go 1.24.3
As I mentioned above, the module name and Go version. Nothing else.
Now, use the editor of your choice and create the main.go
file near go.mod
, with a code like this:
package main
func main() {
println("Hello World!")
}
As Hello World as it gets.
You'd better get used to shortcutting everything. There's no time to type
function
- it's afunc
now.
I guess it's to prevent people from mixing Go with JavaScript. I don't like it, but it is what it is.
What is important here is the package name. A package is the smallest unit of organization in Go. It's basically a container for code and a means of encapsulation. Kind of like in Java.
Now let's execute the code:
go run main.go
Or, if we’re in the same folder, we can just run:
go run .
The runner will scan the folder in search of the main
package
and then execute the main
function(public static void main
sends its regards).
This should print Hello World!
.
Interesting? Not really.
Worth mentioning that while a directory defines a single package, Go has a neat convention for tests. You can put test files (e.g., something_test.go) in the same directory. If you use the same package name (package main), tests get full access. Or you can use a suffix like
package main_test
to restrict tests to public APIs only. These_test
packages reside in the same folder but are compiled separately, providing high cohesion of source code while ensuring tests only use the public API of the package under test. Clean and flexible.
Now, let's perform a little refactoring — extract the Hello World!
logic into a separate function.
package main
func Hello() {
println("Hello World!")
}
func main() {
Hello()
}
Noticed capital H
in func Hello()
?
This is how primitives (functions, variables, structs, interfaces, etc.) are exposed outside the package.
Sort of like public
and private
(package private to be exact) modifiers in Java.
Capital letter — public, lowercase — package private.
Everything within a package is visible to all members of the package.
Easy enough?
How about a little trick?
package main
func Hello() {
println("Hello World!")
}
func main() {
helloFunc := Hello
helloFunc()
}
This is, of course, an absolutely valid Go program.
The function Hello
was assigned to a variable and called using ()
on the next line.
And since it's just a variable, it can be passed around.
Functional and effortless.
Now let's build it:
go build main.go
This command compiles our wonderful Go program into a native executable.
File main
without any extension should appear nearby.
Let's execute it:
./main
Voilà!
Native Hello World!
How about that, Java?
No Docker, no JVM, no GraalVM, no separate runtimes at all.
Pure efficiency instead.
Want to forget about silly, untestable, unreadable, unmaintainable build scripts?
Use Go.
Now, let’s turn this into a library.
Go’s dependency system is laughably simple: it’s just Git.
Every library is just a Git repo, and its version is a Git tag.
That's it.
No complexity.
For example, if I want to use github.com/mfenderov/go-hello-world-lib
version v0.0.1
,
it means there should be a Git repository with that URL
and a commit tagged with v0.0.1
.
No third-party Artifactories, no Maven Centrals, no npm repositories with mysterious outages.
The library points straight to the source.
Elegant simplicity at its finest.
But does this mean that every Git repo is automatically a Go library?
Actually, yes - any Git repo with a proper Go module structure can be used as a dependency.
However, to make your library more discoverable and provide official documentation,
you should list it on https://pkg.go.dev/
.
This step isn't strictly required for usage, but it's good practice for any library you want others to find and use.
How do you get your library listed?
Ridiculously easy.
You need a publicly accessible Git repo,
tag your commit,
and execute the go list
command.
The whole process looks something like this:
git tag v0.0.1
git push origin v0.0.1
GOPROXY=proxy.golang.org go list -m github.com/{your_repo}/go-hello-world-lib@v0.0.1
This tells Go's module proxy to fetch and cache your library, making it available to the entire Go ecosystem.
To test it, initialize a new Go module with the go mod init
command
and get the dependency via go get github.com/{your_repo}/go-hello-world-lib@v0.0.1
.
This is the most straightforward way to release a library I've ever seen.
With some basic CI/CD sprinkles, the whole publishing "process" just disappears.
Go also has a built-in dependency updater:
go get -u ./...
Bam, and all our dependencies will be updated to the latest versions. By default, it updates only minor or patch versions of all dependencies. I ran it every once in a while, just to be on top of my things.
I'm not here to convert anyone. Java will always have a special place in my heart, and honestly, it pays the bills for millions of developers. But after a year with Go, I can't unsee what I've seen. The simplicity, the consistency, the fact that I can actually read decade-old code without a PhD in software archaeology—it's liberating. I'm completely sold on Go's ideas, and right now, I have zero desire to write any Java again.
Sometimes the most radical thing you can do is choose simple over clever.
Sometimes, you just need to let go.