26 ms·
One year after switching from Java to Go
- honkycat 2y agoAfter dealing with constant build issues between Java and Typescript and Node and Python: I love go so much. The package management alone makes it worth it.
- scubbo 2y agoAfter dealing with constant build issues with Go, I hate Go so much. The fact that they've conflated "source code" with "consumable library" means that you need a special case in your CI to publish new versions of a library in Go vs. every other language that builds and publishes an executable, and any tooling that pulls from a private repository has to hack `git config` rather than authenticating like you would any other artifact repository. And that's before we even get onto the "v2" nonsense[0], because apparently it's unidiomatic to continue developing your packages after you publish them. Actually.....given that this language arose at Google, I may be onto something... [0] https://go.dev/blog/v2-go-modules https://go.dev/blog/v2-go-modules
- diamondfist25 2y agoBtw js/ts, python, and golang, which one do you find more productive with?
- scubbo 2y agoHard to pick between Python and TypeScript - they have different strengths. TypeScript's type system is more useful (you'd hope, given the name!), but Python "just works" more often for simple use-cases IME (though I have well over double the lifetime experience with Python, so that might be a me-factor rather than a language factor).
- fullstackchris 2y agoBaffling. If you're seriously saying package management for python "just works" and for go "always breaks"... I guess your either a troll, or you've never written production software. And I'm not talking some clever Jupiter notebooks used for some internal auditing or whatnot, I'm talking about customer facing software used by _at least_ 100 people. I'm 12 years in the game and Python has always caused me nothing but pain, while Go hasnt ever cost me a second of afterthought, working in multiple environments after nothing more than git clone. But, if you like making "virtual environments", a horrible shim gimmick which wasnt even supported by the language itself originally, be my guest!
- honkycat 2y agoAgreed! python easily has the worst package management out of all of them!
- scubbo 2y agoNo, that wasn't what I was saying. I was asked a tangential question about which language I preferred; and considering the language _itself_ rather than the ecosystem, I find that Python is the one that typically "just works" - that is, the barrier between thought/design and expression is smallest. I've repeatedly heard terrible things about Python's packaging, and I have to believe that they're true, even if I've never experienced them myself. And, yeah, you've guessed partially correctly - my own 14 years of professional experience have been primarily with Java and TypeScript, with Python being my language of choice for _personal_ projects. So - yes, I never have written production Python. To once again be clear, I'm not making any claims that Python's packaging is better than Go's. I'm making two separate and unrelated claims (because the latter was prompted by a tangential question): * For someone currently building development tooling for a polyglot company, Go's dependency-management system requires more special-casing (both for publication and for consumption) than the others combined. * For me personally, when translating thoughts/algorithms/designs into code (without considering publication), Python is the language in which I can do so fastest and most intuitively. Never having published a package with it, I've never had to engage with that side of things - I believe folks who tell me that it sucks, but from the consumers' side `source .venv/bin/activate; pip install -f requirements.txt` has always works flawlessly for me.
- throwaway894345 2y agoI’m confused by this. Go doesn’t have a “publish a library” step, whereas every other language does. Is “noop” the special case you are referring to? And why are you comparing publishing a library in Go to publishing executables in other languages? > unidiomatic to continue developing your packages after you publish them Are you talking about publishing breaking changes without bumping the major version? Because that seems … like a bad idea in any language?
- scubbo 2y ago> Is “noop” the special case you are referring to? "Well, yes, but actually no". It's _not_ a noop - to publish a new version of a library in GoLang requires tagging a commit in the source code repo. This, in turn, is tricky because there's no way to review a version bump during PR - unlike, say, JavaScript where a PR makes a change to `package.json`'s `version` field (which will be used to determine the version of the resultant built library), there is no in-code representation of what the version "will be when merged" of a change being reviewed. We've resorted to hacking-in a `version.txt` file which is updated and read by our version bump automation. But that's not all! When making a _major_ version bump in a library, you also need to change the `module` line in your own `go.mod` to have a trailing `/v<n>` (e.g. https://github.com/Masterminds/semver/blob/master/go.mod#L1 https://github.com/Masterminds/semver/blob/master/go.mod#L1). Another special case - every other language's generic version bump automation logic is "update <file location> to hold the version", Go's is "IF bump is major, THEN update go.mod to hold the major version". This is separate from the awkwardness that comes from "publishing" via source code vs. publishing to a binary repository, where the difficulty arises from `go mod download` needing to authenticate to GitHub, whereas every other language's build tools authenticate to the location where built libraries are stored (Artifactory etc.) So, yeah, in fairness there are actually two separate annoyances here that I mistakenly represented as the same - "recording version in tags makes it impossible to review the version-bump _of_ a change-in-review" and "publishing source code directly, rather than built libraries, means pulling dependencies from private repositories requires messing with authentication". Forgive me - there are just so many friction points in GoLang's development process, it's hard to keep them straight. > why are you comparing publishing a library in Go to publishing executables in other languages? Eh - I guess my terminology was off there. I considered and avoided using the word "binary" instead of "executable" because idk if every language uses a binary format to represent the result of building their libraries. The distinction I was trying to draw was between languages that have a transformation process (building/compiling/assembling/whatever) which turns "source code" into "a <thing> that other code can depend on", and GoLang which...doesn't do that. To be explicit - yes, in all cases I'm talking about building and publishing the consumable representation _of_ a library repository. > Are you talking about publishing breaking changes without bumping the major version? No, I'm not. I agree that breaking changes need to be accompanied by a Major Version increment. I'm talking about how cumbersome it is in GoLang _to_ bump the major version that you depend on of a dependency library. You don't just have to update the dependency in your go.mod to `github.com/foo/bar v2.0.0`. Because the import path _includes_ the version, you need to update all the `import` statements throughout your code to use the new `github.com/foo/bar/v2` path. Pointless busywork - even worse than the endless `if err != nil` statements which can be defended on the tenuous grounds that they prompt an author to think about how to handle their errors - this import path change is _truly_ useless as a method to catch errors because, if there is a syntax-breaking change in the depended-upon code, that will be caught at build-time _anyway_. An update to the major version of a library that you depend on is a Big Change, and should be treated with caution. I'm not advocating for doing it blindly or casually. But I don't believe that forcing a basically-cosmetic change to all the import statements _of_ the consumed module does anything to ensure caution.
- pseudoramble 2y agoI’ve been out of the Java scene for a really long time, but will be coming back to it soon. I’m curious - these performance issues described here, are they inherit to how Java itself? Is it baggage from Spring/Boot? Are there ways to get more bang for the buck with some careful choices in a system like this? The closest I’ve done to Java recently is C#, which I think may have similar challenges, but overall didn’t seem quite as bad if you avoided lots of framework extras. It also wasn’t something I was digging into deeply though, so perhaps I’m mistaken.
- xxs 2y agoSpring by far - Spring is effectively a build tool running at run time (scanning, enhance, code generation, etc.) - most of it is just startup as JVM does a decent job at optimizing the cruft. In most cases the boot times don't matter, though - at least for most people, esp. when it comes to production. (it's mostly developers time wasted) I have some personal experience optimizing Spring to record previous runs and effectively cache resolution and code generation, etc. for massive boot latency improvements. Never got around contributing it back, though (not a real spring user)
- jamesfinlayson 2y ago> most of it is just startup Yep, I have some Spring code in AWS ECS and it hits 100% CPU usage on start-up before dropping back to 1.5% when idling (this is with 1 vCPU I think). But yeah I remember reading one of the Spring devs say that some (a lot?) of the runtime reflection could be done at compile time but isn't.
- xxs 2y ago> Spring devs say that some (a lot?) of the runtime reflection It's a lot more than reflection, if it'd have been reflection alone - it'd be markedly better. (and yes, lots and lots can be optimized). Spring effectively: scans the classpath for resources - that includes jars, file system loads every single class matching the scanned directories as a byte array parses it in java (not by JVM) to check what annotations it has (it doesn't load the classes actually) builds dependency tree enhances the previously loaded byte arrays, i.e. generates different byte code loads the newly enhanced classes and create instances (usually through standard reflection) makes calls like PostInit (life cycle) in some cases it uses the standard java reflection to set fields/call methods; in lots of cases java reflection is generating (and loading) a new class (byte code) to carry the process all the steps above can be recorded on run time (or be a step in the build process) and let the JVM just load the classes organically. as for the 100%, spring initialization is mostly single threaded - so likely you dont have many cpus dedicated to the java process. (or you meant just a single core 100%)
- heluser 2y agoI 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 2y agoWhich of these languages is declarative? Aren't they both imperative?
- CharlieDigital 2y agoMaybe Java when using decorators?
- heluser 2y agoJava 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-java/ 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”.
- idoubtit 2y ago> Java 8+ is basically a declarative language. That's a bold claim! The article you link to does not contain the word "declarative". It simply states that modern Java allows more of Data-Oriented Programming, meaning a emphasis on pure data structures (records) and more expressive types (algebraic types). It doesn't say much about the code that deals with this data, which is of course procedural. Apart from SQL which is not generic, I've toyed with two declarative languages, and I can't see much similarities with Java. https://en.wikipedia.org/wiki/List_of_programming_languages_by_type#Declarative_languages https://en.wikipedia.org/wiki/List_of_programming_languages_...
- pm90 2y agoThe startup time comparisons may not seem like a big deal but having an app take several seconds just to start up can burn you really bad in incidents where you want to roll new application versions. And yes, it is possible to engineer around it but I think a better question to ask is why these apps take so goddamn long to start in the first place. There should be some kind of compile or runtime flag to speed this up.
- MarkMarine 2y agoYou can AOT compile Java just like go: https://news.ycombinator.com/item?id=30859013 https://news.ycombinator.com/item?id=30859013
- skeeks 2y agoThere are a lot of applications where the startup time of several seconds does not matter at all. More likely, for most applications it does not matter. Of course, if you are FAANG, it does, but you should not optimize for that in the beginning.
- pm90 2y agoI have never worked for Faang and have seen this be an issue in every single company Ive worked for. Every one. You don’t need millions of pods. Even a fleet of a few hundred (which isn’t crazy for most small to medium businesses) will cause you much pain if you don’t handle this properly.
- yodon 2y agoHas anybody spotted a similar story of switching from C# to go? As someone who is very fond of C#, I'm definitely curious what I'm missing out on.
- fullstackchris 2y agoProbably if you are fond of C# you wont like Go. I've always found C# incredibly verbose and full of all sorts of syntax sugar (that they add to yearly) making code everywhere look different, just enough to add a little cognitive overhead... Go on the otherhand, is purposefully opinionated and forces one more or less to write Go in a certain way.
- neonsunset 2y agoIt’s mostly the other way around. Go is a strictly worse, caveman experience after C#. In it’s best moments, Go is a sidegrade at most.
- fullstackchris 2y agoGo look at httpclient for .NET core 4.8 I mean, just the star count on GitHub is enough to show what the developer community thinks.
- neonsunset 2y agoThere is no such thing as “.NET Core 4.8”. There is no such thing as HttpClient’s GitHub repository either - HttpClient was never a separate project and was introduced into the standard library 12.5 years ago as the replacement to then aging WebRequest which had been around since version 1.1. If you’re interested, .NET’s source code is hosted here: https://github.com/dotnet https://github.com/dotnet with the main repositories being runtime, roslyn, fsharp and sdk.
- WuxiFingerHold 2y agoI've used C# years ago, then some Go for simple web services and CLIs and now I'm back at C# for those applications. You're missing out nothing if you're already familiar with C#. Go's biggest advantage is that it's much simper to learn. The whole experience feels much more lightweight and straight forward. Very easy to navigate the ecosystem. Other than that, modern C# and .NET has the edge over Go almost everywhere. Good examples are obviously LINQ, null safety, extension methods, the type system in general, on the .NET side Generic Host (.NET standard solution for DI, config and logging for all kind of apps), Minimal API, EF Core, and performance. Memory usage for AOT is also very low, Go might have the edge here.
- rvz 2y agoAgree. Go is a joy to use. Java is okay but struggles with scaling unless you have the money to burn for this. To scale up servers in Java requires spinning up highly priced servers with lots of RAM, which that is a lot of money. Using a low spec server is cheaper but you will get more JVM crashes and have to waste time doing more JVM tuning to prevent them. This is not even talking about having lots of 'microservices' to scale which that also means more money spent per month. Using Go just reduces all of that and saves lots of money and is extremely efficient enough to rely on and it directly compiles to a single binary for deployment. From a cost and efficiency perspective, Java is just not the answer even though it is a sound language, unlike JavaScript or TypeScript. Given a choice I'd rather use Java or even Kotlin or JS/TS. But the cost reduction, performance gains and onboarding experience with Golang is hard to beat for backend. Would stay really far away from JavaScript or TypeScript for anything backend.
- amazingamazing 2y ago> Would stay really far away from JavaScript or TypeScript for anything backend. IDK, TypeScript types are extremely ergonomic, and if you're not overzealous makes everything very readable, and given many things are I/O bound anyway, it doesn't matter much.
- CharlieDigital 2y agoBig problems for APIs though because TS types disappear at runtime. So now you need to add in Zod or Valibot as well to validate your types. If you have view models and DTOs, then you add more Zod. Might as well use a statically typed language? Working with OpenAPI is much easier when you already have a static type system. How about database transactions? Ran into an interesting issue this week. In a .NET world, every ORM can participate in an ambient transaction because everyone uses System.Transactions. Not the case in Node because there's no such thing. Would not build backends in TS except for true microservices. The ergonomics of a Nest.js vs .NET Web API are night and day.
- amazingamazing 2y ago
- deepsun 2y agoSeems like they wrote slow memory hogging software and didn't make any attempt at optimizing it. E.g. there's no mention of AOT compilation for Java. No hints at what actually consumed 2GB of RAM. Greenfield projects are always more fun.
- winrid 2y agoProbably it only uses 100mb of heap but they didn't check or tune the memory manager at all
- deleted 2y ago[deleted]
- bryancoxwell 2y 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 2y agoI 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.
- arnath 2y agoThis 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?
- MrDarcy 2y agoI was writing code similar to how the popular int13 kubelogin kubectl plugin works, which also uses wire for DI and is organized as a clean architecture repo. In that particular case I found both the clean architecture and the wire DI to add more layers of abstraction, which took more time to comprehend, write, and maintain than jettisoning both and doing it with idiomatic Go.
- blibble 2y agoseems to be mostly criticism of spring rather than java the company behind spring should ruin go by porting their crappy library to it (oh look, it's broadcom....)
- MarkMarine 2y agoHave you seen Fx (uber dep injection for go)? If you want to have the imprint of your keyboard on your forehead, try to get a complex Fx app working after you’ve refactored. Pure, absolute misery. I yearn for dagger every time I touch the system that has it, but black magic that my IDE understands is a close second dagger compared to a dep graph that you can only figure out if it’s correct by running it. Real fun on a go app that can’t be run locally.
- computerdork 2y agoAgreed, the spring framework is completely against the spirit of Java. Yeah, auto-wiring was a terrible idea - why do I have to guess what components are going to be pulled in? And why do I have to figure this out a runtime? The features of Spring Boot are nice, but Spring itself should probably be put out to pasture. Someone needs to write a good alternative to Spring and start promoting it like crazy (probably something exists).
- bdangubic 2y agowent to springone conference in vegas in 2016. eight years later, there are still no good alternatives :)
- computerdork 2y agoSad, spring is killing java
- bdangubic 2y agoif it is, it is doing a very, very, very bad job of it
- epolanski 2y agoVery ignorant about Go, is dependency injection not a thing there? E.g. I like declaring interfaces in other languages that model some service (e.g. the database), focus on the API, and then be able to provide different implementations that may use completely different technologies behind them. I know that some people don't see the point, but I'll make an example. At my primary client the database layer is abstracted and provided at runtime (or compile time, depends) with the repository pattern. How's that useful? Developers can use a filesystem-based database when working, and don't need to setup docker images that provide databases, anything they do is saved on their machine's filesystem. E2Es can run against an in-memory database, which makes it very simple to parallelise tons of different tests as there are no race conditions. And yes, the saved formats are the same and can be imported in the other databases, which is very useful for debugging (you can dump the prod database data you need and run it against your implementation). There's many other situations where this is useful, one of the core products I work on interfaces with different printing systems. Developers don't have printers at home and my clients have different printing networks. Again, abstracting all of it through DI makes for a very sane and scalable experience. I don't see how can this be achieved as easily without DI. It's a very nice tool for many use cases.
- gt0 2y agoDI exists in Go, but it's not ubiquitous like it is in the C# or Java worlds. Last time I used Go, I used "wire" for DI and was pretty happy with it.
- cflewis 2y agoI worked on Wire right at the beginning, happy to answer any questions about it.
- badrequest 2y agoNo questions, but a heartfelt thank you. I've used wire for years and plan to continue doing so. :)
- gowk 2y ago
- smrtinsert 2y agoI thought we had moved on from flame bait articles as a species
- MarkMarine 2y agoI work with a brownfield go monorepo with a couple different styles and lesser known “frameworks” in it. It sucks to work in, one of the previous devs was a huge fan of clean code, so every function with more than a couple inputs has its own struct, pointers are used everywhere just by default (and basically never nil checked) and the AI generated tests are so plentiful that changing a small thing quickly explodes into thousands of lines in of changes. Because it’s go and not really following a framework or pattern, the LLMs just can’t get the style right, so everything is brute force. Know what is easy to build with an LLM? A spring boot app. You can work on the hard logic while your little automated friend works on all the boiler plate and wiring.
- rollulus 2y agoDisasters can be created in any language, right? Has little to do with Go.
- matsemann 2y agoTrue, but most of the arguments against java in this thread are similarly about bad dev practices and not the language itself.
- MarkMarine 2y agoExactly.
- evil-olive 2y agoit's really weird to see a "Dependency Injection & Context" section as if Golang's context has anything at all to do with DI. in particular, reading between the lines here: > 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. suggests to me that they've implemented one of the Golang anti-patterns that I find most annoying - overloading the context and using it as "grab bag of pseudo-global variables" in this anti-pattern, you have an HTTP request handler, and want to make a database query, so you need access to the database connection pool. having a global variable for the connection pool feels wrong...so what many people do instead is call context.WithValue in their server startup code to put the connection pool into the grab bag, and then in the request handler call ctx.Value to pull the connection pool out of the grab bag. the Golang docs [0] explicitly say not to do this: > Use context Values only for request-scoped data the much better way, in my experience, is to make the request handler a method on a struct, and then the struct holds references to things like the connection pool. this can also be done with closures and captured variables, of course, but that tends to get unwieldy for non-trivial usage. if you do this, then your "dependency injection" in Golang tends to look pretty much identical to how it would look in Java, if you wired everything up by hand rather than using a framework/library. and then if you want, you can use a library such as Fx [1] for automatic Dependency Injection along the lines of Spring Boot. 0: https://pkg.go.dev/context#WithValue https://pkg.go.dev/context#WithValue 1: https://github.com/uber-go/fx https://github.com/uber-go/fx
- paulddraper 2y ago> as if Golang's context has anything at all to do with DI Yes, Go's context can be used as a DI container.
- tomcam 2y agoI thought it was mostly used to terminate an asynchronous event?
- paulddraper 2y ago
- blindriver 2y agoI'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 2y agoMaybe 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 2y agoExceptions 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.
- qaq 2y ago99% of people who never written go will know what the go version does
- milne-dev 2y ago[dead]
- owlstuffing 2y agoGoing 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/manifold-deps-parent/manifold-delegation/README.md https://github.com/manifold-systems/manifold/blob/master/man...
- dagss 2y agoAre 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 2y 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.
- throwaway2037 2y 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"?
- TheCycoONE 2y agoWe run mostly Java apps with a few Go apps. What I miss with Go, maybe just because I'm not as familiar and don't know where to look, is all the runtime analysis that's built in. Thread dumps, heap dumps, and even flight recorder profiling is all built in to the JVM so it works with all apps everywhere. When a Go app suddenly slows down it's very difficult to determine why unless the app was coded to provide the right metrics.
- burch45 2y agoI actually much prefer go ‘s runtime tooling. Pprof has everything I need built in; heap, cpu, blocking, mutex contention. And don’t need additional tools to visualize the collected data. https://pkg.go.dev/net/http/pprof@go1.24.0 https://pkg.go.dev/net/http/pprof@go1.24.0
- cmrdporcupine 2y agoI haven't done Java fulltime in almost 15 years, but I still haven't seen anything out there that is as good as JMX was, out of the box. For just getting decent metrics / observability without rolling in frameworks. Just part of the runtime.
- metaltyphoon 2y agoDoes pprof work when CGO is enabled?
- fooster 2y agoYes
- philipwhiuk 2y agoAs 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 2y agoI’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?
- cmrdporcupine 2y agoMost "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.
- rahen 2y agoCold latency is an issue with microservices. If you need to use Java, you'll likely end up using frameworks like Spring or Quarkus, which somewhat diminish the advantages of using the JVM and Java as a language. At that point, you might as well start with Go from the beginning.
- codr7 2y agoI'm pretty fond of Java; it's definitely a superior language to Go if you ask me, which I've also written a ton of code in. But I stay away from Spring Boot, end the entire EE stack of crap that came before it, if at all possible. I've had more success adding whatever I need on top of embedded Jetty. It's mostly a cultural problem, no one is forcing you to go the AdapterFacadeInjectionBuilderWhatever way. I've been working on a library to simplify interfacing with relational databases for a while now. With several implementations in Go and other languages. And the java version looks at least as nice as the rest to my eyes: https://github.com/codr7/tyred-java https://github.com/codr7/tyred-java
- 38 2y ago[flagged]
- codr7 2y agoI could tell you why, based on writing a ton of code in both, but I doubt that would lead anywhere.
- 38 2y agohttps://github.com/codr7/tyred-java/tree/main/src/codr7/tyred https://github.com/codr7/tyred-java/tree/main/src/codr7/tyre... 24 of those files are under 100 lines - some of them are as small as three lines of code. and that's not a personal preference - that's mandated by Java that each type needs to be in its own file, ridiculous.
- linkerdoo 2y ago[dead]
- rr808 2y agoThe best thing about Go is no Spring framework. I like Java but finding Java projects without Spring is difficult.
- jjav 2y agoWhat is forcing the use of Spring? I've been a frequent Java programmer since its earliest days (started around 1996) and I've yet to ever use Spring. None of the bad parts of Java are actually a part of Java!
- demi56 2y agoBut you see nobody really wants to be “different” if there are 100 people and the first 90 went right the probability of the remaining 10 going right will be higher. Java was designed to be business oriented and I think this is why Go and Java is very different not in terms of language but the way the community makes decisions
- invalidname 2y agoAll k8s operators are written in Go. It's really unsurprising that Java doesn't fit there. Java has huge advantages for typical web applications (observability, deployment flexibility, deep toolchain beyond IDE etc.). I've seen companies trying to use Go in the environment where the JVM excels, then breaking down the problem to thousands of small microservices which end up making something simple into shards of complexity. Java is a general purpose application development language. Go is a system language that isn't as deep as Rust. They are very different things and comparing them doesn't make any sense. Like the people comparing Rust to JavaScript, these are not interchangeable.
- anta40 2y ago>> 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. Yeah. I see Android app development is still mostly dominated by Java/Kotlin. Of course you can do it with Go, e.g: https://fyne.io https://fyne.io. Never try to write something serious with it, just messing around with the examples.
- newAccount2025 2y ago> 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. You are working too much.
- user9999999999 2y agoAny suggestions for how to store request scoped data without context? Specifically when using middlewares, like in the example of an auth middleware, needs store isAdmin or isLoggedIn or UserId
- alpb 2y agoPresumably you control the entire handler stack in Go. So you can extend the HandlerFunc signature with parameters customizations to carry explicit structs, or return “error”, for example.
- time4tea 2y agoThe 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.
- khana 2y ago[dead]
- okeuro49 2y 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 2y 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.
- gf000 2y ago
- jayd16 2y agoIs it wrong that I judge anyone that calls DI "black magic"? Clearly it's just computers all the way down and even spring DI isn't that hard to follow. It's one thing to call it bloated or annoying or tedious but why are we proud to announce we didn't do the work to figure it out?
- psychoslave 2y agoWhat do you call DI here? Black Magic is doubly negative here, but I guess that it can fueled with the feeling that less automagic in a codebase helps downstream debug and moving to maintenance mode, as it increase the chance to let a quick grep reveals immediately where the associated code is.
- kebsup 2y agoI'd consider a lot of spring annotations "black magic" , because you can't simply go to definition and see how/what they do.
- gf000 2y agoYou... literally can? Yeah, you may have to grep for the annotation's name, but it's not like it's hidden/closed source/whatever.
- hu3 2y agogrep for annotations... That mentality is how you get death by a thousand cuts. Cognitive load matters.
- gf000 2y agoSearch within your IDE, do a Google search, whatever suits you. What mentality? And the cognitive load is to RTFM, so that you understand what are you doing. If that leaves any questions you can attempt to do a deep dive. It's not particularly high cognitive load to know that @GET is a get rest endpoint. How is that different without annotations? Documentation is also your best bet at first in case of a normal library function call. Jumping into that codebase can also be quite involved, depending on what it does.
- raffraffraff 2y ago> We even went so far as writing infrastructure code for Kubernetes clusters that automatically provision apps in Kotlin. Dear god. Compared to the go code required to do the same thing, this is crazy. Edit: I removed reference to terraform, seems like there's no infrastructure code here, it's helm + operator.
- rzz3 2y agoA coworker once claimed that Go is the new Java, and I haven’t really been able to refute it.
- pjmlp 2y agoThis 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.
- linkdd 2y agoAnd 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 2y agoTo 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.
- btreecat 2y agoThis is why I don't buy the FP hype
- ReflectedImage 2y agoGolang's standard library looks pretty complete to me and they will probably master it in a month.
- WJW 2y agoThe mindset that you can master any programming language in a month is exactly what I meant in my previous post. There's a veritable cottage industry of "golang pitfalls" blog posts out there that show there are absolutely a lot of footguns in Go. For example, what do you think the following should print? values := []int{4, 8, 15, 16, 23, 42} for value := range values { fmt.Println(value) } I don't think there are very many people who would guess the integers 0 to 5. I also like the following one: ch := make(chan int) ch <- 1 fmt.Println(<-ch) What would this print? The only correct answer is sadly "fatal error: all goroutines are asleep - deadlock!". Golang is a fine language and simpler than most, but sadly "simpler" is not the same as "simple".
- ninetyninenine 2y agoGod I hate working with Java developers on go projects. They try to introduce “design patterns” into the whole ecosystem and twist golang into doing something it wasn’t designed to do. It’s a bit too late because tons of golang libraries are like this now.
- wiseowise 2y agoI wish this “DI black magic” meme would die. If you’re afraid of runtime DI, then use Dagger 2.
- KronisLV 2y agoNever really heard of Dagger before, but I love what I'm seeing: https://dagger.dev/ https://dagger.dev/ On the other hand, using DI with Spring is both powerful and really annoying when things blow up due to unsatisfied dependencies, I'd much rather see that at compile time, so Dagger seems right up my alley! Thanks for mentioning it!
- TheSmoke 2y agoI wonder how a migration to for example Micronaut from Spring would look like in their case. It is optimised for startup time, performs the dependency injection and annotation processing in compile time as well.
- ed_blackburn 2y agoThe 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 2y 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.
- never_inline 2y ago> heavyweight ecosystem Try micronaut once. It does DI and even ORM mapping (with micronaut data jdbc) at compile time and avoids most reflection.
- aqueueaqueue 2y agoIoC 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 2y agoSo is DI usually referring to automatic framework-wiring DI? Cause I constantly pass dependencies in as arguments and call it DI.
- deleted 2y ago[deleted]
- DeathArrow 2y agoI am sure Go has many benefits, but coming from .NET I don't see a big improvement in switching to Go. .NET feels less verbose, it's batteries included, has an AOT compiler, tons of libraries, starts fast, has very good tooling and performance wise it compares well to Go. Also, it can be used for more than web apps and command line tools. Where I see a benefit, though, is using go in a large greenfield project because Go is very easy to learn and you can attract Java, .NET, Python, Javascript and even C and C++ developers so you can assemble a team fast. Though in a microservice context it might not be a large benefit.
- mickael-kerjean 2y agoThe big change is cultural. The places I've seen using .NET felt like cults to me, a monoculture were everyone must run windows, swearing by how powershell is the shell of the gods, ms sql being the only true data store that's the best solution for 100% of every use cases, azure being the best cloud provider, and everyone forced to use vs code because of all its fancy integration with even the pm tools.
- neonsunset 2y agoWhat are the mythical .NET cults which swear by Windows, PowerShell and MSSQL you speak of? Every other ".NET shop" I know nowadays deploys to Linux hosts/container images hosted wherever and develops on a variety of systems where the OS of choice has become mostly a non-factor.
- deleted 2y ago[deleted]
- DeathArrow 2y agoDepending on how Java application was devoped, the real benefit might be breaking free from the kingdom of nouns, land of design patterns, country of OOP.
- pshirshov 2y ago1) There is a perfectly working AOT compiler for JVM, namely Graal Native. Sub-second startup times are easily achieavble. 2) Dependency Injection does not require run-time reflection, I made one reflectionless DI for Scala and one for C# 3) Spring is not the best DI in the Java ecosystem
- watt 2y agoDagger will give you DI at compile time (build time), via annotation processor feature. (And it seems Quarkus can do it as well?)
- zwnow 2y agoThere's one simple argument to never touch Java: FactoryFactory produce ArgumentFactory, ArgumentFactory.builder().build(argument)
- spintin 2y ago[dead]
- countrypao 2y ago[flagged]
- teruakohatu 2y agoWas this summary written by an LLM?
- countrypao 2y agoYou got it, this summary was based on my fragmented thoughts, which I then refined and structured with the help of an LLM.
- darkhorse222 2y agoI have never been able to rid myself of my earlier career instincts that people who spend time debating the pros and cons of languages are not really builders. I am a computer engineer, I have very little respect for coding languages since ultimately they always come down to the same fundamental computing mechanisms: processors and memory. Perhaps when efficiency is needed I can understand. But most of the time seeking efficiency is yet another non-builder priority. I have always found efficiency comes from good thought, not good tooling.
- Mekoloto 2y agoThis doesn't give me any clue how it would really feel like to switch a big webapp/enterprise webapp from Java to Go. How is the handling of database and entities? How is the security model and support for api endpoints? etc. I have had very little issue with java startup time. Hot code replacement exists for decades
- DrScientist 2y agoNever quite understood the attraction of dependency injection frameworks. Sure pass in your dependencies as an interface via some sort of constructor, but why all the frameworks to do so? Why all the complexity with hard to debug magic strings, annotations and finding out what's missing from the classpath at runtime? Just seems as a very complicated way to avoid creating package c that brings together package a and dependency b in a simple class that creates b and passes it into a. What am I missing?
- HexDecOctBin 2y ago> What am I missing? Job Security.
- mrkeen 2y agoI second this. The original value proposition of DI (dependency inversion) was that you could write your tax logic and unit-test it without the code knowing that it's talking to MySQL. Injecting your dependencies into your constructor makes dependency inversion possible. That messaging has somehow become smeared across dependency injection, frameworks, classpath scanning, autowiring, reflection, annotations etc. 20 years ago, DI meant that your TaxCalculator didn't know about your database. Today, DI means that your TaxCalculator still knows about your database, but now it knows about Spring too.
- wink 2y ago> Never quite understood the attraction of dependency injection frameworks. Many years ago there was a running joke that a "dependency injection framework" in PHP was a single array with stuff in it. Then it all became much more java-like.
- tored 2y agoDependency injection containers can be overly complex and allow complex configurations, too much magic. That is why I kept my container library simple, you read thru all the source in less than 15 minutes. What I like about it is that it is natural to use composition with classes, it encourages to create classes to divide your application into reusable parts. And with my library all you do is to put the dependent class in the constructor. My library can without much effort be replaced with normal factory, that is by design. Warning PHP https://github.com/paketphp/bero https://github.com/paketphp/bero
- staticelf 2y agoI work at a company that is based on a large java application. The main app takes around 20-30 seconds to startup and while it probably does a lot of stupid shit that have accumulated over the years I believe this is more common in javaland. The culture around java is kind of enterprise, do everything unnecessary complex with strange patterns you've never heard of etc etc which makes the everything unnecessary slow and complex. Where as other languages like node or go small libraries is the culture so it's not weird that it is what it is. I have many times suggested to use simpler tools or frameworks like Javalin but to no avail. Java devs love their spring. Instead of just doing the thing you want to to, with java devs, you have to follow pattern that makes you create a several classes and interfaces when in reality it could be a couple of lines.
- cs02rm0 2y agoI really liked Dropwizard. It had a philosophy of picking a set of tools that could help people get up and running with a java web application quickly and that would serve them well for a long time, choosing the best tool for the job. Then Spring boot came along where they defaulted to the much fatter Tomcat, apparently just to be different, and then went down the list of libraries and instead of picking the best one, they picked the Spring one. I never understood that philosophy, but it won out. It looked at lot to me like no one ever got fired for choosing IBM/Microsoft. But it was so much worse. Other frameworks have improved on what Dropwizard offered for startup times, etc. but still don't seem to have gained the traction to unsettle Spring.
- anthonybsd 2y agoLarger than 2GB RAM JVM containers? It sounds like the author didn't really explore any of modern container-ready frameworks and blamed the ecosystem. Move from Spring Boot -> Micronaut or Quarkus and compile your code into into GraalVM image and you get sub-100MB containers.
- deleted 2y ago[deleted]
- mrbonner 2y agoStart up time of the JVM is 8s? I would definitely say that it depends on how their service initialization work. I have seen service accesses database to warm cache, calling other services to initialize their configuration, or simply reading config files before fully serving. And all that take time! There is no way against that regardless of your PL or runtime. A typical no network initialized service in Java would start under 500ms (a p90, give or take).
- monksy 2y agoThere are some arguements that were made in this post that don't matter at all. One of those is: Cold start time This isn't a big deal in the context of a web app. 8s vs 100ms. If you're scaling up a service. 8s is not a big deal in the scope of fixing a horizontal scaling issue. Additionally, what it's considers bloat: Yes, you should optmize your app. It depends on the context of your application. Brining up a rest app that needs to stay up..who cares if it takes .5-1gb of ram. Also, I didn't see it pointed out about the concerns of the lack of 3rd party frameworks. I find this disjointed and sparse frameworks to be more of a concern for a language/ecosystem than what the author pointed out.