Readit News logoReadit News
time4tea · a year ago
The jvm is a pretty insane beast. It will do usage based recompilation, escape analysis for memory, so non heap allocation is super fast, has great memory safety... But a lot of people use it with spring/spring boot, a technology designed to work around the complexities of a particular type of middleware software in the late 90s and early 2000s. It's cargo cult programming of the highest order. In the OP, the author is comparing apples with oranges, with a bit of misunderstanding that java/jvm means spring boot, and while that is true for a lot of people and certainly a lot of stuff on the internet implies that 'this is the way', it's not required. Startup times of ~100ms are absolutely standard for a big program, similarly unit tests taking 1ms. I prefer to write kotlin rather than java, as it's a nicer language ,IMHO, but still those bytecodes run on Jvm and same stuff applies.

Edit: im not advocating writing 'ls' in java, and I would also agree that java uses more memory for small programs, so its not a systems programming language probably.

Just use new() it's pretty fast.

unscaled · a year ago
I think we can sum it this way. The blog post writer's intuition was correct: if you write two equivalent Go and and JVM programs, the Go program would use less heap memory and have faster startup times. What they are incorrect about is the extent of these claims.

It is obvious that most of the memory and startup overhead in their software comes from Spring, rather than the JVM. The JVM is probably not an ideal platform for writing an Kubernetes infra tool like operator or a sidecar (mostly due to heap size and more a complex binary packaging story), but using Spring Framework for writing these kind of tool is a bigger problem. If they just wrote their initial tool in Kotlin without using Spring framework at all, it would be much faster. They could even have kept Dependency Injection with a lightweight framework like Koin or Dagger (even a reflection-based framework like Guice performs worlds better than Spring).

I would probably still prefer Rust for writing most infrastructure projects nowadays. GraalVM Native Images has similar memory footprint to Go and solve the slightly troublesome JAR deployment story, but with the complex license terms for the enterprise edition (where most of the performance optimizations are), I don't feel safe using it. I also prefer Rust's concurrency model and I find cargo to be a far more pleasant experience than either Maven or Gradle, but in the end of the day, you will not see serious issues if you use Kotlin in a sane way, and you can even save on some Go boilerplate.

AYBABTME · a year ago
Rust has its uses but why would you write infra code in Rust when Go is used for most of it, and is just much more ergonomic and fast to work with. The iteration times with Rust are quite detrimental. On the other hand, most of k8s' ecosystem is in Go.

I don't like commenting in language-war territory things but I found your comment surprising. "Rust or JVM" for infra isn't a dichotomy I would expect.

indemnity · a year ago
Funnily enough the initial version of Kubernetes was written in Java! But refactored into Go early on https://archive.fosdem.org/2019/schedule/event/kubernetesclu...
smusamashah · a year ago
At work we work on a Java code base 20 years old and it is written like C. No dependency injection or other Java Web development like shenanigans. Almost every lib etc has been built in-house. It runs an MMO, it's fast. Its just way more productive/faster to work and implement something in the Java codebase than a C++ codebase that we have.

Someone shared a Job posting which asked for "no java experience". It was funny.

watt · a year ago
If you write Java just like you would write Go - meaning, no reflection shenanigans, use Jigsaw to cut out unneeded JVM stuff, or use GraalVM and compile natively (which you will be able to, because you did not use any of JVM reflection magic), Java will absolutely be competitive.
theshrike79 · a year ago
It's kinda understandable. I've seen "Java coders" write Python for example.

The first thing they do is create a class and maybe even a Factory or Interface.

You can see instantly where their experience is from and it's hard to unlearn.

procaryote · a year ago
Yep.

Java is really good. Java developer culture is awful.

If you instead of spring boot just pick a few dependencies you really need, you don't throw the whole Design Patterns book at it just because you can, and you don't try to make everything changeable without recompiling or redeploying, it's pretty nice to work with

tombert · a year ago
I have said that for awhile: the worst thing about Java is Java developers.

I have some issues with the Java language (though Java 21? Actually pretty ok!), but there's no question that there's a lot of great stuff in regards to libraries in Java land.

A lot of the stuff that's just built into the JDK is already very good, for example. NIO can be a bit hard to work with, but is generally very good, fast, and reliable. A lot of the concurrency abstractions (e.g. BlockingQueues) are really pleasant to work with for most concurrent programs, the different types of mutexes/locks give access to most of the patterns you want, and the thread-safe collections like ConcurrentHashMaps are very boring, in that they work pretty much exactly as I want them to.

If we extend to third party libraries, it's even better. Vert.x and Disruptor, for example, are downright excellent tools for wrangling concurrency.

The issue is that it feels like a lot of Java developers are stuck in 1999; it can be like pulling teeth to even use NIO, which isn't exactly "new" at this point. When I wrote some stuff using BlockingQueues instead of throwing `synchronized` everywhere, people acted like I was grabbing this was some exotic code from a distant land that had never been tried before, When I imported Vert.x into my project because I needed to run a lot of non-blocking concurrent tasks, it required a lot of justification for using it instead of using threads everywhere.

pjmlp · a year ago
That is actually enterprise culture, using Go won't make a difference in the enterprise space.

Ever looked into Kubernetes source code?

cies · a year ago
Like the article I hear Spring Boot here mentioned again. I also really hate the annotation culture. This is big in Spring Boot, and more common in Java since it is so damn verbose.

It is not inherent in Java though, and the Kotlin "developer culture" seems to be much more annotation averse (as we all should be).

divan · a year ago
> Java is really good. Java developer culture is awful.

First we shape our tools, then our tools shape us.

mooreds · a year ago
At $CURJOB, we have built a pretty good business on an app built in Java. We don't use Spring, but instead a lightweight MVC of our own creation[0]. It is open source but I don't know if anyone else is using it :) .

I once asked the founders why they chose java; it was because it was 2015ish and they had an existing product in java and they knew it. Which makes sense to me. I think there's a lot of path dependence to language choice.

I also think the domain matters. For database based webapps, Java can be great. Lots of tooling and knowledge around it. And modern java is pretty friendly to write. Plus, if you are interacting with something over an API and deploying it via a container, who really cares what it is written in?

For kubernetes operators? Seems like a natural fit for golang. Anything kube really. I had a friend who ran a k8s consultancy for a while and said that they'd prototype stuff in python because it was easy, then implement in golang because that was what was consistent with the rest of the ecosystem.

0: https://github.com/prime-framework/prime-mvc

Deleted Comment

okr · a year ago
Spring boot is just a tool. People seem to forget and take for granted the knowledge that is hidden there. Sure you can wire everything together yourself, but who does that? It would be such a waste of time. I rather wait these 5 seconds for a long running service, tbh.
renegade-otter · a year ago
https://notes.ericjiang.com/posts/751

"PayPal and Wal-Mart have also had high-profile switches to Node.js. Of course, they’re comparing two completely different things to make Node.js look better. In these too-good-to-be-true stories, they’re switching from a gigantic enterprisey codebase to a Node.js app written from scratch. Is there any question that it wouldn’t have been faster? They could have switched to pretty much any anything and gotten a performance gain.

In LinkedIn’s case, they had proxies running on Mongrel with a concurrency of 1. It’s like switching from using one finger to type on a QWERTY keyboard to using ten fingers on a Dvorak keyboard and giving all the credit to Dvorak for a better keyboard layout."

P.S.: Google became so awful that a search for even the direct title of the article does not land it in the top results - just references to it.

okeuro49 · a year ago
> But a lot of people use it with spring/spring boot, a technology designed to work around the complexities of a particular type of middleware...

No, people use it because we don't want to reinvent the wheel.

Spring is well documented and Spring Boot gives you a set of dependencies that all work together.

Then you don't have to spend time messing around with things like OAuth and authentication, you can just write the application.

anthropodie · a year ago
> Then you don't have to spend time messing around with things like OAuth and authentication, you can just write the application.

It sounds good but in reality people end up spending time messing around with config files and annotations.

smrtinsert · a year ago
Don't know why this is down voted its absolutely true. When our tech leadership reviewed the numbers we changed auth design without touching the application code base really. Saved a ton of work considering we have many microservices.

Spring rocks.

cies · a year ago
I found String (Boot) a horror to work with. Annotation based devt with super weird errors. The great thing was that most errors where run into by many before me so could be solved by a simple web search/ Stack Overflow article.

To me being able to CTRL-click my way into the libraries is very important. Overuse of annotations (a.k.a. magic) breaks that. It is what monkey patching is for Ruby. The beginning of the downfall IHMO of an otherwise great language.

Luckily Kotlin's culture avoids this.

whstl · a year ago
I don't see how writing "one more OAuth client" or "one more login thing" using Spring Boot is not reinventing the wheel in itself.

If you really care about "not reinventing the wheel" there are ready-made paid solutions such as Auth0 and Cognito, plus self-hostable open-source options like Keycloak, Authelia, and Dex.

Also, Spring Boot itself uses third-party libraries for OAuth and Authentication, like Ninbus, which people can drop-in in their non-Spring Java apps.

optician_owl · a year ago
> Spring Boot gives you a set of dependencies that all work together.

But spring boot deps is infamous meme.

> Then you don't have to spend time messing around with things like OAuth and authentication

Yeah. The funny thing is reality is quite complicated and spring supports a lot of (almost) documented cases. But 99% javaspring developers do not care. I met quite a lot of experienced devs and only 2 of them know how to optimize application start or which errors Kafka wrapper would not retry and so on. Half of the non-default situations are solved via reinventing the wheel because of a lack of understanding of nuances. I can't say people are dumb, many of those devs are smart. I tend to say that ultra-framework kills people's expertise and in the long term hardly saves resources.

dominicrose · a year ago
I tried truffleruby for the adventofcode challenges. Almost everything ran faster with normal ruby. I got a stack too deep error in truffleruby that I didn't get in normal ruby. I don't recall having more problems with memory with one or the other. There was one case where truffleruby was really useful though.

Thus I'm with you that it's not a systems programming language. It seems good for processes that stay ON, like servers.

But then when compared with PHP, during development you don't need to worry about the server, it just takes the most recent version of your code. Surely hot-reload can be a solution with java, but in practice it's a complication, especially if you're part of a team/project where there is no hot-reload support.

Twirrim · a year ago
The JVM doesn't attempt to compile a method until a method has reached 10,000 executions. So it's extremely likely your code ran the entire time in interpreted mode. Even if a method did get flagged as hot, you've then got the time taken to do the actual compilation to native code to contend with.

If you're trying to write systems code and want to use the JVM, GraalVM can do full ahead-of-time compilation and will get you almost instant start up with all methods natively compiled.

HumanOstrich · a year ago
I don't follow. Seems like the issues you had were due to truffleruby and how it implements ruby's internals on the JVM.

That's not a Java/Kotlin/JVM issue.

gf000 · a year ago
Why would you expect any speedup for programs that run for a split of a second?
boreas7878 · a year ago
Yep, he is comparing the most bloated enterprise framework to lean Go apps. This article is so wrong in confusing Java with their particular monster project. You can definitively AoT-compile in Java, there are modern lightweight frameworks like Javalin, and I think the language itself now allows to write web services without any library.
coldtea · a year ago
>Edit: im not advocating writing 'ls' in java, and I would also agree that java uses more memory for small programs, so its not a systems programming language probably.

I dunno, some databases and things like Lucene are written in Java.

725686 · a year ago
"cargo cult" really? Have you ever built maintained big a Java codebase? Spring is a marvel that sprang out of the experience of very, very talented people. It can be misused, of course, like any other technology, but it is a huge productivity booster.
cies · a year ago
You are joking right?

"Spring is a marvel that sprang out of the experience of very, very talented people." --> sounds very culty to me.

And yes I maintain both Spring and non-Spring JVM projects.

itsthecourier · a year ago
yeah, this author reminds me of the guys who think simpler is better because they haven't read the whole documentation on why the thing is complex in first place, do they end up reinventing the wheel badly as they start to grow and discover problems already solved in mature frameworks

the lack of specific mention of scenarios and features beyond dependency injection suggests ignorance IMHO

CharlieDigital · a year ago
I find this very common when working with JS backends. Once you get serious, you end up replicating a lot of the complex concepts of frameworks like Spring or .NET
zerr · a year ago
Do you suggest to use vanilla Java instead of Spring?

Dead Comment

bryancoxwell · a year ago
> 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.

Man. This works. The context API allows/enables it. But I’d really recommend against passing data to functions via context. The biggest selling point of Go to me is that I can usually just look at anyone’s code and know what it’s doing, but this breaks down when data is hidden inside a context. Dependency injection is entirely possible without using the context package at all, interfaces are great for it.

MrDarcy · a year ago
I hit this point in tfa and had the same comment. Please don’t pass things around in a Comtext. Maybe stash a slog logger in there, but that’s about it.

I made the switch to Go a few years ago. For those who are on a similar journey as the author, or the author himself, I suggest spending time with the Go standard library and tools written by Rob Pike and Russ Cox to get a handle on idiomatic Go.

It’s clear the author still thinks in Java, not go. Saying Context ctx for example instead of ctx context.Context. Also DI, which is arguably not necessary at all in Go given how elegantly interfaces work.

I spent quite a lot of time using wire for DI in go only to really study the code it was generating and realizing it truly is code I would normally just write myself.

Edit:

Regarding stack traces, it turns out you don’t need them. I strongly suggest a top level error handler in Go combined with a custom error struct that records the file and line the error was first seen in your code. Then wrap the error as many times as you want to annotate additional lines as the error is handled up to the top level, but only that first point in our own code is what actually matters nearly all of the time.

_w1tm · a year ago
> Also DI, which is arguably not necessary at all in Go given how elegantly interfaces work. > I spent quite a lot of time using wire for DI in go only to really study the code it was generating and realizing it truly is code I would normally just write myself.

DI is the idea that you should create your dependencies outside of the module / class / function that uses it and pass it in. This makes it easy to swap implementations.

DI does not require any framework and I would argue you can’t write modular code without it. Most likely you are doing DI even in your manually written code.

mukunda_johnson · a year ago
I prefer stack traces in errors. It's gives so much more automatically so you don't have to worry about manual annotation. Stack traces and debug logs are the way to go. I like to use panics for exceptional conditions just for the convenient escape with the stack trace.
nesarkvechnep · a year ago
I’m yet to see a former Java developer who uses the idioms of the language they currently use. They all just write Java in a different language.
saturn_vk · a year ago
> Also DI, which is arguably not necessary at all in Go given how elegantly interfaces work.

DI is necessary in every language that doesn't rely solely on global singletons. Passing dependencies as arguments to a function is DI.

What may not be necessary, are IOC containers automatically create objects and satisfy their dependencies.

arnath · a year ago
This comment is about a very minor part of what you said, but isn’t the whole point of a DI framework to write code you’d have written anyway to save you time?
nine_k · a year ago
The exact stack trace may not be very necessary, but tracing the chain of calls, especially async, can be hugely helpful in troubleshooting, performance tracking, etc.

In Node, I remember wrapping Promisesromises into objects that had a stack for pushing messages onto them, so that the creator of a Promise could mark the calling site, and creating another Promise within that promise would pick up the chain of call site names and append to it, etc. Logging that chain when a Promise fails proved to be very useful.

akoboldfrying · a year ago
> Regarding stack traces, it turns out you don’t need them.

goes on to suggest rolling your own buggy, slow, informally specified implementation of half of a stack trace printer

eximius · a year ago
> spent quite a lot of time using wire for DI in go only to really study the code it was generating and realizing it truly is code I would normally just write myself.

Yes, but the point is 1) you don't have to write it yourself and 2) it does 'the right thing' even if your junior dev straight out of BS CS wouldn't know how to write it.

There are, of course, caveats and not all frameworks are created equal and any tool can be misused. But I find DI very useful. Personally I'd recommend Uber's Fx framework and underlying dig library.

jbreckmckye · a year ago
> Regarding stack traces, it turns out you don’t need them. I strongly suggest a top level error handler in Go combined with a custom error struct that records the file and line the error was first seen in your code. Then wrap the error as many times as you want

So instead of a stacktrace, you are - tracing the stack? Am I understanding correctly?

Because it just sounds like a manual version of stacktraces

Cthulhu_ · a year ago
> Then wrap the error as many times as you want to annotate additional lines as the error is handled up to the top level

I'd add that this is a last resort; errors should be handled and resolved as close to where they occur as possible. Having them bubble up to a central error handler implies you don't really want to do anything with it.

fweimer · a year ago
How does Go avoid getting bogged down by middleware-oriented programing in practice? I think most large-scale programming in organizations tend to converge on that because it sort of works.

Is it that people who like this kind of stuff write microservices for deployment on Kubernetes clusters?

saturn_vk · a year ago
> Maybe stash a slog logger in there, but that’s about it.

Please don't do this either. Read the stuff you want to log as additional attributes in your slog handler from the context, which you ultimately pass to `slog.*Context`

nprateem · a year ago
> Regarding stack traces, it turns out you don’t need them

Then you go on to explain how to recreate them by hand.

mexicocitinluez · a year ago
> I spent quite a lot of time using wire for DI in go only to really study the code it was generating and realizing it truly is code I would normally just write myself.

This is like saying "I won't use source generators because it generates code I would normally write myself"

Like, yea no shit. THAT'S the point.

cflewis · a year ago
FWIW the internal Google style guide says to not pass anything via Context unless you _really_ know what you're doing and why. Things that make sense: security tokens and tracing. Things that don't make sense: almost everything else.
throwaway2037 · a year ago

    > internal Google style guide
Can we read this somewhere?

Dead Comment

Ferret7446 · a year ago
Context is just thread local storage aka dynamic scoping aka global variables.

It is useful for some things, particularly middleware that needs to cross API boundaries.

https://www.felesatra.moe/blog/2019/12/01/transiting-apis

throwaway2037 · a year ago

    > The biggest selling point of Go to me is that I can usually just look at anyone’s code and know what it’s doing
This is not possible in Java or C#? Both of those languages are so simple to grasp.

lelanthran · a year ago
> This is not possible in Java or C#? Both of those languages are so simple to grasp.

Not really, no.

Apart from the cognitive burden of knowing the test framework in use, the DI tooling in use, the Mocking framework in use, plus all the annotations that each framework may want/need, almost none of which applies to the Go projects I've seen, the languages themselves have drifted away from simplicity.

I used to program in C# but left it for a long time and only recently looked at it again. C# is approaching C++ levels of syntax indecipherability.

zdragnar · a year ago
The languages aren't, strictly speaking, so much the problem as are the massive frameworks configured via distant files with lots of DI and reflection magic involved.

Go has some large frameworks, but the community frequently suggests they aren't needed and that the standard library is enough.

nprateem · a year ago
Try understanding what spring boot is doing. Annotation soup. It's practically impossible for a beginner to Spring to debug.
rapsey · a year ago
Programs written in those languages tend to have many layers of abstractions, on top of abstraction heavy frameworks.
roncesvalles · a year ago
IoC DI in Go is a massive antipattern and absolutely should not be done. Do NOT write Java/.NET style controllers in Go i.e. initializing an instance of a "controller" type with some instances of a "dependency" such as a store.

Just use the dependent package directly. Initialize the package once using init() or Init(). Rely on the built-in package dependency resolver system in Go, which will catch cyclic dependencies, call init() in topo order, and other such things.

Test using monkey-patching. Stop using interfaces just to be able to swap a real thing with a mock implementation. These are all symptoms of writing Java in Go.

someothherguyy · a year ago
So, write python in go instead, gotcha.
akoboldfrying · a year ago
> Test using monkey-patching.

TIL Go has monkey-patching.

But since it has monkey-patching, how is it statically typed? Or does Go monkey-patching amount to creating an instance of a defined-on-the-fly subclass?

The latter would be interesting, because Java lets you do this too -- conveniently "new up" an instance of a subclass that you define inline of a given class or interface (this used to be the easiest way to define callbacks, before lambdas came along), so this testing strategy is available to Java too, but seems not to be preferred.

Separate question: IIUC, monkey-patching is convenient if your test code directly needs to create a TestX instead of a real X, but what if you need the test double "deeper down" -- that is, you need an X that creates a Y that creates a TestZ instead of a real Z?

stpedgwdgfhgdd · a year ago
I don't get the part on package. Often you want to use structs. (Instance vs static)

How would you construct an instance in a test that needs mock implementations?

captain_coffee · a year ago
Hard disagree to literally every thing that you have said in your reply above.
the_gipsy · a year ago
> Test using monkey-patching.

Can you elaborate? What library do you use?

3uler · a year ago
And yet Uber wrote fx[1] to support DI in their golang services. It’s clearly a useful pattern when working on large services.

[1]: https://github.com/uber-go/fx

nine_k · a year ago
Implicit shared state? Exactly the thing to enjoy in a highly concurrent environment!

At least I hope that a context can be immutable throughout. I only see one variable in the documentation of the context package. Extending it with mutable fields, and mutating them to pass data between functions, would be something I'd never approve in a code review.

cyberax · a year ago
The context itself is immutable, it's essentially a linked list (or actually a tree). If you need to "mutate" it, you create a new segment that links to a previous segment.
rendaw · a year ago
How are interfaces a replacement or improvement to context? Is it just that they're type safe?
philipwhiuk · a year ago
As a Java developer...

If the entire problem domain space is written in a language it's dumb not to follow suit. Libraries that solve problems reduce your work to your own specific issues, rather than 'building an apple pie from scratch'.

Java is good right now because most problems have libraries to do what you want. Most formats have APIs.

It's not perfect in any area - the start-up time is a bit lame, you have to write 'anti-Java' to really get close to native performance. But it's quick to build in, the toolchain is solid, the dependency framework works better than all the alternatives. It's a 95% language that's been made development friendly.

(Golang somehow added versioning late and is 'Git+' at best, NPM unpublished stuff, C++ is hell, etc. Rust crates just doesn't have much but seems to have been built properly).

But if you're working in a new space (crypto, AI, cloud) then you should definitely look at what the state-of-the-art libraries are written in.

And you should think real hard before you implement your app in anything else. Because there will be a real, long term, painful penalty unless you get VERY lucky and the entire ecosystem pivots to your language.

pylua · a year ago
I’m also a long time java spring developer. I started writing a game recently and was really surprised about how bad the performance can be when you run it in a tight game loop.

The startup time is also a real problem, as you really want to be able to scale up pods quickly.

That said, it’s good enough right now. You can make it work at scale, and it’s worth the cost trade off of trying to do it more efficiently in a different language.

I would be curious to see how a rust microservice would compare in my companies infrastructure. How much cloud saving could we squeeze?

marginalia_nu · a year ago
Writing high performance Java is definitely a bit of a dark science, a lot of the performance isn't just the code loop you're looking at, but memory allocations matter quite a lot as well. Complex hierarchies of large long lived objects can absolutely tank your performance.

There can also be a lot of performance to be gained by going off heap. I'd be probably be looking at an ECS design around MemorySegments, rather than modelling the game state with Java objects. Though, to be fair, this is how you'd write a game engine in C++ as well.

conwaytwitty · a year ago
Spring Boot startup time is indeed a problem, especially when scaling horizontally on low cpu nodes.

If your environment allows for burst cpu usage until ready to accept traffic, you can start up really fast as spring does so much reflection magic during startup that can't be done during compilation "trivially". You can include hints for runtime configuration from a build, but it doesn't do much to help in really low cpu envs.

Then you can of course just do native images, but you lose some of the spring "magic", and might be annoying to refactor towards.

znpy · a year ago
> The startup time is also a real problem, as you really want to be able to scale up pods quickly.

I was learning a bit of spring last week and a spring boot web application, generated via the web interface boots in like 800msec:

    ...
    Initializing Spring embedded WebApplicationContext
    Root WebApplicationContext: initialization completed in 339 ms
    Tomcat started on port 8080 (http) with context path '/'
    Started DemoCourseApplication in 0.746 seconds (process running for 1.012)
    ...
Reusing my experience from other technologies... I'd say the issue might be in whatever you're doing in your initialization and/or how much stuff you're loading.

Looks like the core spring is decently fast, to me.

gf000 · a year ago
Could you expand on "how bad the performance can be" part?

If you are doing graphics, it is entirely more likely that you do something dumb there - there are many pitfalls.

Also, unless you are doing something very CPU-heavy, there won't be any noticeable difference as web servers are doing IO predominantly. Maybe slightly less RAM usage (but you could also just decrease the heapsize to tradeoff a bit of CPU-time for memory, if it were to make sense).

rootlocus · a year ago
Have you tried AoT compilation with graalvm?
cmrdporcupine · a year ago
Most "microservice" backend type stuff is I/O bound, not CPU bound. You're likely not going to win much.

Maybe get away with lower memory instances though.

throwaway2037 · a year ago

    > I would be curious to see how a rust microservice would compare in my companies infrastructure. How much cloud saving could we squeeze?
So, replace cheap Java developers for expensive Rust devs to squeeze out some savings? It makes no sense to me.

jillesvangurp · a year ago
I made the switch to Kotlin around 2018; after having been using Java since 1995. Java is a choice these days, not a necessity. Kotlin is a drop in replacement for Java in 100% of it's core use cases. No exceptions that I know of. Doesn't matter whether you do Android or Spring Boot. It kind of does it all. And it's actually really good at dealing with creaky old Java APIs. Extension functions (one of the party tricks modern Java hasn't yet picked up from Kotlin) are amazing for adapting old code to be a bit less tedious to deal with.

You don't really lose anything (APIs, tools, etc.); but you gain a lot. I still use Spring. But I've now used it longer with Kotlin than with Java.

And the nice thing with Kotlin is that it is gaining momentum as its own ecosystem. I'm increasingly biased to not touching old school Java libraries. Those lock me into the JVM and I like having the option of running things in wasm, in the browser or on mobile. Our frontend is kotlin-js and it runs a lot of the same code we run in our Spring Boot server. Kotlin multi platform is nice. I've published several libraries on Github that compile for platforms that I don't even have access to. Supposedly my kt-search client (for opensearch and elasticsearch) should work on an Apple watch. I've not gotten around to testing that just yet and I don't see why you'd want that. But it definitely builds for it. I had one person some time ago trying it out on IOS; same thing (I'm an Android user).

Ecosystems are important. But it's also important to not get too hung up on them. I say that as somebody that made the bet on Java when there was essentially no such thing as a Java ecosystem. It was all new. Kind of slow and wonky. And there were a lot of things that didn't quite work right. But it had lots of people working on fixing all of those things. And that's why it's so dominant now. People are more important than the status quo.

Sometimes you just have to cut loose from the past and not go with something safe but very boring like Delphi, Visual Basic, Perl, and several other things that were quite popular at the time and didn't quite make it. They're still around and there's a half decent library ecosystem even probably. But let's just say none of those things are obvious things to pick for somebody doing something new that was born this century.

Go as an ecosystem is definitely large enough at this point that I would label it as low risk. Same with Rust. Neither is going anywhere and there are plenty of well motivated people working on keeping all that going. Same with Java. All valid reasons for using any of those. But nothing is set in stone in this industry. A lot of things people were using in the nineties did not age well. And it will be the same in another 30 years. Most of those old people that will never change retire at some point. Java projects are pretty depressing to work on these days for me. Lots of grumpy old people my age. I've had a few of those in the last few years. Not my idea of fun. The language is one thing but the people haven't aged well.

philipwhiuk · a year ago
Most of my new back-end code is actually Scala these days which I have a very love hate relationship with.

Kotlin. I don't have much experience with. I know it's fairly lightweight syntactically.

I'm unconvinced by cross-compile to web stuff so far. Scala.js looked very ugly.

throwaway2037 · a year ago
Yeah, language ecosystems get no love here. Part of me dreads Java, but the developer experience is mostly worse elsewhere.
pjmlp · a year ago
This looks like one of the typical "we switched from A to B, whithout actually mastering A, so B is alright" kind of posts.

Just on the monitoring part, Go has nothing even close to VisualVM, Flight Recorder, JRebel, VM agents, JMX.

No mention of AOT compilers, JIT caches, and so forth.

zipy124 · a year ago
I like to think of these problems as having skill ceilings and skill floors like in video games. For large companies often the skill floor is more important than the skill ceiling. If it is easier to start in B than A, even though after years of experience A can be much better, it might still be a better choice to use B.

When you have lots of junior devs, you are more concerned with this upstart time, and I think in this case for example, Java makes it very easy to write poor code, or use poor packages. Yes a master in Java can take advantage of the extremely mature ecosystem with tools like those you have mentioned, but for those who are not experienced like you, they may get better productivity out of what you see as the less powerfull tool.

ivan_gammel · a year ago
I do have experience of managing junior teams and I think Java is actually great for them. You only need one senior person to kick off the project and put down the rails - a relatively small effort if you use frameworks instead of bespoke solutions. The team just needs to follow the lead. It is extremely cost efficient and good enough for business in terms of quality/security/performance.
linkdd · a year ago
And it's fine. Why continue using something you don't master?

Yes you could try to master it, but it takes years. If another tool compensates your lack of mastery, why not use it?

WJW · a year ago
To me the implication is that the culture at this company won't allow this team to master Go either, and in a few years there will be a post describing how they moved from Go to another language.

Many people like to write about how Golang is so simple, but the drawback of that simplicity is that many features of other languages are either covered by additional dependencies or by inflating code size. It's just as possible for Go projects to devolve into big balls of mud as for any other language.

lenkite · a year ago
There are a lot of leanings to master for Go as well which they may have not discovered.

As an example, the Go runtime does not honor container resource limits. You would think this would be one of the very first fundamental features supported out of the box by an advertised "cloud native" language.

nprateem · a year ago
Because like any shortsighted decision it can come back to bite you later.
unscaled · a year ago
I think most of the tools on the second line are not highly applicable to a Kubernetes operator, or otherwise would just use the "Cloud Native way of doing things".

Something like JRebel would generally be a big no-no for Kubernetes, where the container image of the operator is expected be immutable (at least in terms of the code running in there). It may or may not be okay for developing an operator, but that's certainly venturing into a bit of an unknown territory.

VM agents and JMX are usually replaced by explicitly adding Open Telemetry support and/or a Prometheus endpoint. It's certainly more boilerplate and manual work, but so many more things are explicit in Go, that's just something you've got to buy into. If you're using Rust or Typescript this could be a lot more automated but that's not the case for Go.

Advanced profiling is probably something you'd have to give up, but I doubt a Spring shop would be deeply into profiling. The low hanging fruit is to just throw away spring and get an immediate 200%-400% performance boost.

But the JVM is still a more mature platform. Throughput is better on the JVM than on Go and some things are very troublesome in Go (basically anything that requires code generation).

My personal "cool tool" that I can run on the JVM but doesn't have a mature equivalent in Go is PIT[1] for mutation testing. But not everybody will be using mutation testing, just like not everybody will be using JRebel or JMX. And it doesn't mean everybody should. If you're a Spring a shop the thing you'll be probably missing most in Go would be things like Dependency Injection, Java-like ORMs, reflection magic and Spring Configuration Server.

[1] https://github.com/hcoles/pitest

kitd · a year ago
pprof data via HTTP is part of the Go stdlib and is pretty close, good enough for 95% of cases. One could argue you need those tools less in a Go codebase anyway ;)

AOT is mentioned and JIT is not relevant.

pjmlp · a year ago
pprof isn't not even close to what Visual VM and Flight Recorder are capable of, hence why then there are stuff like open telemetry integration, which require active coding to provide sensible data.

This is the only mention of AOT

> 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.

Zero content on all the options since Excelsior JET and other commercial alternatives came to be, or the free beer alternatives available nowadays.

JIT caches and plain old JIT aren't the same.

JIT caches are similar in concept to AOT with PGO, with the difference that instead of doing that once for the lifetime of the executable with data from test runs, it gets updated after each production execution.

Deleted Comment

xxs · a year ago
Pretty much. There are lots of post in that form, it makes for a good self tapping on the back and few blog posts - without any questions answered.

OTOH - if that's what makes run the business, I'd not argue.

Ygg2 · a year ago
I'm not sure who said it, but the quote went something like this: Programmers are at fault for the current status in Computer Science, rather than working on expanding their knowledge they keep chasing the latest fad.

If someone knows which cankerous Computer Scientist said this, I'd appreciate a reply.

llm_nerd · a year ago
Totally orthogonal, but it's also a super weird piece in that it starts by listing three authors, but then has several paragraphs giving a personal account of a single person's history, as if someone wrote their blog entry and then other people demanded a slice of the credit.
IshKebab · a year ago
I mean, they don't mention monitoring... so so why would they need to master it? How would mastering monitoring tools help them with the problem that Java startup time is orders of magnitude slower than with Go? Especially without having to do any extra configuration work.
owlstuffing · a year ago
Going back to first principles, nominal typing is what I miss most with Go. I get the utility of structs + interfaces + structural typing, but most of the time there is more benefit in declaring that a type nominally implements an interface when that is the intention. Code is far easier to read and understand that way, both for developers and tooling.

I suppose exclusively structural typing would be more acceptable if Go supported _real_ interface composition, like Scala with traits or true delegation via the manifold project[1] for Java. But that's missing as well e.g., does not inherently fix the Self problem, etc.

Considering Go's initial goal, which was IIRC a better systems language, then yeah, sure it's an improved C. But now that Go is routinely compared with Java/Kotlin and friends, I personally don't see it, particularly wrt the type system, to be taken seriously as a Java contender. Shrug.

1. https://github.com/manifold-systems/manifold/blob/master/man...

dagss · a year ago
Are you aware of the trick of "var _ foo.RequiredInterface = myType{}" to make the compiler enforce that a struct implements a given interface?

Is what you seek a nicer syntax for this or does what you speak of bring something more feature wise?

At least IntelliJ IDEs will always make it clear what interfaces all your structs implement.

owlstuffing · a year ago
>Are you aware of the trick

Yes, and while it uses the compiler to ensure a type implements an interface, it's still a trick that exposes a large hole in the language... and begs for it to be filled. Most importantly, it still doesn't make the code much easier to read and understand.

>At least IntelliJ IDEs will always make it clear (or less foggy)

Indeed. Go benefits hugely from IntelliJ, or to be specific the IJ plugin API.

_ph_ · a year ago
While having to declare the interfaces a type implements can increase the readability, it comes with a huge drawback: you are limited to implementing the interfaces considered when writing the code. In my eyes it is a fascinating and important feature that you can add interfaces and all existing types automatically implement them, if they, by their methods signature, implement them. You don't have to edit their type declarations to do that.
owlstuffing · a year ago
>it comes with a huge drawback: you are limited to implementing the interfaces considered when writing the code

Huge drawback? Quite the opposite. Unintentional implementation is a terrible trade for readability and type-safety, hence the trick the prior commenter mentioned.

In fact, structural typing is best when limited to the far less used case where an interface is designed to reflect functions from existing types. It's useful here, otherwise it's a hinderance for the primary case where a type _intends_ to implement an interface, which is what nominal typing is for.

Go was designed as a systems language, and the choice for structural typing served that goal in terms of performance enhancements. But used as a general purpose language, as the trick above (and others) demonstrate, Go's structural typing is less suitable compared with nominal typing.

Deleted Comment

throwaway2037 · a year ago

    > But now that Go is routinely compared with Java/Kotlin and friends
Do you think Go is "moving up" (away from systems prog) or is Java "moving down"?

owlstuffing · a year ago
> Do you think Go is "moving up" (away from systems prog) or is Java "moving down"?

I haven't been keeping score, but I've read other articles like this one claiming Go as an enterprise language alternative to Java. Not much concerning Java as a systems language, but that makes good sense. I see Go v. C/Rust, not Go v. Java/Kotlin. Just my take.

heluser · a year ago
I long for a deep article about the same topic. The real, core difference between Java and Go for backend is declarative vs imperative coding styles.

This one, as typical for such articles, repeats typical secondary talking points and even makes similar mistakes. For example it conflates the concept of DI with specifics of implementation in some frameworks.

Yes there are older Java frameworks that do runtime magic. But both new Java apps and well designed Go services use compile time dependency injection as a way of achieving dependency inversion.

jayceedenton · a year ago
Which of these languages is declarative? Aren't they both imperative?
CharlieDigital · a year ago
Maybe Java when using decorators?
heluser · a year ago
Java 8+ is basically a declarative language. They even officially started departing from Object Oriented Programming towards Data Oriented Programming ( article by their chief architect https://www.infoq.com/articles/data-oriented-programming-jav... ). Unfortunately, most of the comparison articles come from people who still code POJOs with setters, use for loops and overall rely on mutable and unsafe code.

And using Pike’s own words “go is unapologetically imperative”.

golly_ned · a year ago
Write that article then. Don’t badmouth the writer’s firsthand (evidently not secondary) experience over the course of a year since they didn’t express your idiosyncratic view that Java is a Declarative Language, which is very different from a data-centric language, which itself is a term from the clojure/scala enthusiasm about functional programming around Java 8.
blindriver · a year ago
I've been using Go for a while now. The biggest headache is error handling. I don't care what the "experts" say, having exception handling is so, so, so much cleaner in terms of error handling. Checking for err is simply bad, and trickling errors back up the call stack is one of the dumbest experiences in programming I've endured in multi-decades. If they are willing to add generics, they should add exception handling as well.
BytesAndGears · a year ago
Maybe go just isn’t for you? It really doesn’t need every feature of other languages. The error handling is ideal for me, better than any other language. You are always explicit, with every function call, about “what could happen if this fails?”

Maybe passing it up the stack is the best way to handle it, but also maybe it’s better to handle it somewhere in the middle.

The thing that always happens with exceptions in API projects I’ve worked on, is that exceptions can come from any level of the stack, then by default it skips everything in the middle, and the controller has default handlers for what to do in case of an exception.

If there are exceptions you didn’t know existed because of some library or just complex code with dozens of possible exceptions? They still end up being handled in your controller. You need to know exactly what exceptions could happen at every level of the stack, and how to handle it, otherwise everything just short circuits.

With the go errors, you only need to know “did this function call work? If not, then what?”

hedora · a year ago
Exceptions are a terrible idea.

However, I strongly prefer rust error handling to Go.

go:

   (res, err) := foo()
   if err != nil
       return err
   (res, err) := bar(res)
   if err != …
Equivalent rust:

   let res = bar(foo)?)?;
I think go should add the ? sigil or something equivalently terse.

Ignoring all the extra keystrokes, I write “if err == nil” about 1% of the time, and then spend 30 minutes debugging it. That typo is not possible in idiomatic rust.

bmurphy1976 · a year ago
To each their own. I'm not going to claim to be an expert, but as somebody who's been coding since the 80s it was a breath of fresh air to see Go do what I wanted languages to do all long instead of ramming exceptions down my throat. I have problems with Go (examples: slice behavior and nil type interfaces) but error handling is not one of them.
CharlieDigital · a year ago
What challenge did you run into with exception handling?

I'm curious because I've never felt it being onerous nor felt like there was much friction. Perhaps because I've primarily built web applications and web APIs, it's very common to simply let the exception bubble up to global middleware and handle it at a single point (log, wrap/transform). Then most of the code doesn't really care about exceptions.

The only case where I might add explicit exception handling probably falls into a handful of use cases when there is a desire to a) retry, b) log some local data at the site of failure, c) perform earlier transform before rethrowing up, d) some cleanup, e) discard/ignore it because the exception doesn't matter.

rollulus · a year ago
I guess I won’t be adding new information to what your “experts” said as well, but hey. I love Go’s error handling. Syntactic sugar like in Rust could’ve made things a bit nicer to they eye, but apart from that: being forced to think about each error path leads in my view to better code. Compared to a fat try/catch and fingers crossed that all goes well in it.
codr7 · a year ago
Agreed, every time I jump back into Go I'm at first relieved at how nimble it feels; but it never takes long to remember what a pita error handling is or the kludges you need to write to do basic collection transformation pipelines (compared to Java/Streams, C#/LINQ, C++/std etc).
throwaway2037 · a year ago

    > or the kludges you need to write to do basic collection transformation pipelines (compared to Java/Streams, C#/LINQ, C++/std etc).
Why hasn't anyone written a good open source for this problem, now that Go has generics?

ed_blackburn · a year ago
The real win for this team isn’t just switching from Java to Go. It’s breaking free from the heavyweight framework ecosystem that the JVM all but forces on you.

It’s not that the JVM is bad or that Go is a silver bullet, but Go does act as a forcing function, pushing teams to write simpler, more efficient code without layers of boilerplate, indirection, and unnecessary IO.

You can still do inversion of control without an IoC container—instantiation works just fine! Look at Go’s HTTP middleware pattern with structural typing and first-class functions. No config files, no annotation magic, just composition, testability, and code small enough to hold in your head.

anthropodie · a year ago
> It’s breaking free from the heavyweight framework ecosystem that the JVM all but forces on you.

> No config files, no annotation magic, just composition, testability, and code small enough to hold in your head.

This. When I look at code I should just be able to follow it and know what's happening. The whole annotation magic and config files makes it hard to understand the flow of things.

thecupisblue · a year ago
I wouldn't see this as the win for the team. The real win here would be:

* Understanding where the issues and bottlenecks are coming from * Understanding how it came to these issues * Figuring out how to resolve the issues without changing the whole tech stack

That way, the team would end up with a deeper understanding of their organisational issues leading to it, understanding of their code, cost of services, cost of the ecosystem and cost of computation. They would have been able to apply this knowledge to other projects in the company running the same stack.

The way this is written is basically sweeping the problems under the carpet with "tech slow, new tech fast" solution, while simultaneously letting the team keep doing whatever it is they were doing that made this slow. This is nothing but a surface level rewrite without understanding the real cause of the pain.

never_inline · a year ago
> heavyweight ecosystem

Try micronaut once. It does DI and even ORM mapping (with micronaut data jdbc) at compile time and avoids most reflection.

hyperpape · a year ago
> the JVM all but forces on you

I don't think you're wrong, but you're making this sound like it an issue with the JVM. It's certainly not. I guess it could be an issue with the Java language, but I don't really think so.

Mostly it's an issue with the Java ecosystem.

chamomeal · a year ago
Yeah sounds more like a java issue to me. Clojure is usually beautifully terse and straight-to-business with very little boilerplate, and it usually runs on the jvm.

But still agree with the other commenter on the benefits of go. I’m not a huge fan of go, but it’s definitely encourages grug brain style (https://grugbrain.dev/) which has many benefits.

In fact, the only time I disagree with grug style is when I’m really close to the perfect generics for a TS function with lots of coupled inputs. Always so close…

aqueueaqueue · a year ago
IoC isn't even DI. IoC is saying "I need to talk to a service that handles these account operations I care about" rather than "I need a MySql connection, a coupla s3 buckets and a folder on disk"

DI can support both the "I need" and "I orchistrate" patterns.

Obviously modulo leaky abstractions! You might want to known if that account code is in L1 cache or Timbuktu.

chamomeal · a year ago
So is DI usually referring to automatic framework-wiring DI? Cause I constantly pass dependencies in as arguments and call it DI.
gf000 · a year ago
> pushing teams to write simpler, more efficient code without layers of boilerplate, indirection, and unnecessary IO

Ergo, more code, much more development time, more chance for bugs, for questionable benefits (there are an endless number of web applications running on Django. If python is fast enough to tell the OS what IO to do, then surely enough Java with two indirect calls (which you will likely end up doing in your hand-written implementation as well) will be more than adequate.

Also, I don't buy that more code is easier to hold in one's head. An annotation that literally declaratively says what that thing is is much easier to grasp and maintain.