time4tea a day 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 day 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 day 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.

      • sn9 a day ago

        Discord had a pretty famous transition from Go to Rust [0].

        [0] https://discord.com/blog/why-discord-is-switching-from-go-to...

        • derekperkins 15 hours ago

          They didn't have particularly great reasons to migrate at the time besides the team wanting to write rust. That article is just trying to provide validation

          • sn9 5 hours ago

            "With the Go implementation, the Read States service was not supporting its product requirements. It was fast most of the time, but every few minutes we saw large latency spikes that were bad for user experience. After investigating, we determined the spikes were due to core Go features: its memory model and garbage collector (GC)."

      • packetlost 21 hours ago

        Eh, I don't think iteration times in Rust are nearly as bad as you think. During a realistic development loop compilation is <1s in most situations thanks to incremental compilation and tests are fast unless you have synthetic slowdowns such as sleeps or IO (which would effect both languages equally).

  • smusamashah a day 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 day 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.

      • wvh a day ago

        In the end it all boils down to higher-level logic compiling down to machine code that has to run. A language's culture and philosophical choices are tied to the language itself as much as syntax or compiler, so maybe the largest difference between languages like Java and Go is really the whole developer attitude and aesthetic choices.

    • theshrike79 a day 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.

      • kuschku a day ago

        Java developers may write verbose python, but that doesn't compare to the crimes actual python devs commit.

        Currently trying to modernize a python project that doesn't use modules, just executable python files that import each other with custom sys.path hackery, which is also used for globals, no type annotations, GLib used for everything including math and string to int parsing.

        • CBLT a day ago

          Hey, sorry, that might have been me. I did that then left the company.

          In my defense, I had to hack around a different Python library also manipulating sys.path, which nobody likes except this one dev team in a different timezone. They somehow got a director to declare that I would fix this issue they self-created before they woke up in 8h, and I wasn't allowed to rip out the library. So, ugly sys.path manipulation in the exact way that library wants. Not proud of it, but it sounds like you were given time to engineer an actually correct solution.

          • kuschku 9 hours ago

            The project is an open source project, so it's likely not your fault :)

            I can fully see how and why this might have grown historically in ye olden days of python2, but it's not sustainable to continue adding floors ontop of a rotten foundation. Code needs maintenance like anything else, and far too often there's no budget or time available for it. Even if that maintenance would reduce the overall workload.

        • Twirrim a day ago

          People can write bad code in any language

          • gorjusborg a day ago

            It is almost like language is a conduit for something, I wonder what though. I will have to think on it.

        • hlpn a day ago

          Do we work at the same place?

      • skapadia a day ago

        That is funny, because I was a Java developer for many years, then Scala for a few years, and these days I mainly write Python, but the last thing I go for is creating a class. That's generally only when a set of functions in a single responsibility module need to share / mutate some state, and it's more self-documenting than passing around dictionaries.

  • procaryote a day 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 day 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.

      • p2detar a day ago

        Agree with everything that you wrote. The saddest thing is the job market. It would not be a lie to say the 90% of the jobs list Spring/Boot as a requirement. It's like a framework equals the whole language. This cannot be a good sign. I'm thankful enough to have around 2 decades of Java experience without touching anything named Spring and boy I can tell you I have been having lots of fun with Java. Using Vert.x right now and I like it a lot.

        • tombert a day ago

          I don't do web stuff (and hopefully that can stay that way), so I've managed to avoid a lot of Spring stuff, but I am stuck using Spring Streams for some Kafka stuff I'm doing. I'm not going to lie, I don't love it, but that's mostly because it feels like all I do is write YAML files all day.

          Vert.x is a lot of fun, and I've gotten pretty decent performance with its SQL libraries in particular, though I'm not sure I'm a huge fan of the EventBus. I've had better luck with LMAX Disruptor, at least for the data-processey stuff that I do.

          But as I said, there's a ton of really great libraries in Java. The language isn't perfect, but Java 21 fixes a lot of my gripes, and it's nice to have a ton of really well-tested libraries to minimize how often you have to reinvent the wheel.

    • pjmlp a day ago

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

      Ever looked into Kubernetes source code?

      • monksy 21 hours ago

        I think Go adds to the enterprise culture and it does so on a global scale. Go is all about a consistent code style and a reduction of language complexity where your hands are tied on how you write code.

      • throw-the-towel a day ago

        At a former job, my coworkers successfully wrote Java in Python.

        • tombert a day ago

          I haven't touched it since it was still called "AngularJS", but I remember AngularJS kind of felt like they were trying to make it so you write "Java" in "JavaScript". Ember too, if I remember correctly.

        • zellyn a day ago

          Tell me you worked at Google without telling me you worked at Google

    • mooreds a day 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

    • divan a day ago

      > Java is really good. Java developer culture is awful.

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

    • cies a day 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).

      • whizzter 6 hours ago

        Annotations were adopted to cut down on the massive amounts of XML that was used to configure everything beforehand, still a lot was loosely wired for "extensibility" and people have learned that it was 99% overengineering so that's cut down these days in various ways.

      • peterashford 20 hours ago

        I think this is why most criticism of Java sounds weird to me. I've used Java since 1.3-ish and I've never used a web framework or an annotation and I think I've used a factory once (I think there's one in Swing and I recall thinking it was a stupid design choice).

        • cies 11 hours ago

          Like I say:

          > It is not inherent in Java though

          "Coupling by annotation" culture (which diminishes many of the benefits of using a typed language, as it pushes coupling to runtime and by means of reflection) IS inherent to SpringBoot. Hence my distaste for it.

          Java's okay, prefer Kotlin though these days.

      • gf000 a day ago

        You do realize that Java is objectively less verbose than Go? Even on a vanilla language to vanilla language basis, but let alone against something like Spring Boot that does almost everything for you in a typical CRUD application.

        Annotations are declerative shorthands. How is a trivial spring boot endpoint with methods with a single @GET line above them denoting the endpoint verbose?

        What about a single SQL query in an annotation above an interface method's name? Will your whole implementation of connect to db, execute query, iterate over the resulting rows, and convert them to some native object/struct shorter than.. 2 lines?

        • tombert a day ago

          > You do realize that Java is objectively less verbose than Go?

          I might agree if you're speaking about Java since ~11 or so.

          One of my biggest complaints about Java, especially before the lambda syntax caught on, was how many extra files it made me create. I felt like every project had lots of wrapper classes, and people would make a make a file for each class.

          You could argue that that was "bad" Java and they should have nested the classes, and that's fair enough, but it certainly wasn't uncommon Java. It artificially makes the language feel way too verbose if you have ten files more than would be necessary in a comparable language.

          Once people started to embrace the lambda syntax, I feel that Java got immediately more fun to write; you weren't constantly creating one-off instantiations of interfaces or classes (well, at least not explicitly anyway), and the language felt considerably more streamlined.

          Java 21 is actually one of the most pleasant surprises I've had in quite awhile in the tech world, in that I'm actually having fun writing Java. Sealed interfaces and record patterns make some code considerably more pleasant, and I actually have been writing Java in my free time, which is something that I would have said would never happen if you had asked me five years ago.

          ETA:

          https://youtu.be/jgkEHoc3YUw?t=288 See! I even said I would never enjoy writing Java!

          • javanonymous 20 hours ago

            > which is something that I would have said would never happen if you had asked me five years ago.

            I think a lot of people are noticing the changes Java has had in the previous years. The language has made a lot of improvements, and I feel that the mind set of the community has changed. The old enterprise way of factories and unnecessary abstractions have lost a lot of popularity, and is mostly still alive in legacy software/teams and universities who have not yet caught up.

            Even Spring Boot is now a valid approach for getting sh*t done for startups. There are of course frameworks that are more light weight, or you can start from scratch and choose your own libraries to keep the size down. But SB is simply good enough for most use cases, and even supports native compilation now.

            • tombert 19 hours ago

              I still am not a huge fan of the Spring stuff, I have to use Spring Streams for work and I think it’s unpleasant to work with. It seems like the rest of the world has much more fun configuring YAML files than I do. I had to use Spring Boot at a previous job and it wasn’t for me, but honestly I really just hate working on web stuff.

              But that’s obviously not the language’s fault. There are frameworks in Java that I think are great, like Vert.x; hell even going super low-level with NIO is straightforward enough if I really need control of HTTP stuff.

              The stuff I really have the most fun working with is concurrent and distributed programs, and I think Java (or at least the JVM) is pretty hard to beat with that. Vert.x, Disruptor, and even the built-in JVM concurrency libraries (other than synchronized) are excellent; they have a Just Works quality to them.

              And nowadays, GraalVM is good enough with its native compilation that you can avoid the long startup times and keep the memory under control, so it even is reasonably ok for custom command line tools.

        • cies 20 hours ago

          Annotations do many things.

          Sometimes --in Spring Boot-- they hook different systems up with eachother: this makes the framework very "magic". I really dont like that: I want to be able to CTRL-click my way to understand how everything works!

        • FrustratedMonky a day ago

          "Annotations are declerative shorthands."

          OR. Are annotations a crutch for something that should be in the language.

          Just generally, if some tool has to use annotations, then that is indicator of something that should be in the language.

          • gf000 a day ago

            So which language has native REST endpoints? Session-aware security?

            Like, this is just standard metaprogramming, if you don't have it you will just reach for dumber tools like non-language aided code generation.

            • FrustratedMonky a day ago

              all right, maybe it is a spectrum.

              On other end

              Type annotations in JavaScript. Just use a language with Types.

    • okr a day 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 day 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 day 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 day 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 a day ago

        Like, what other option is there? There is either a proper, battle-tested solution which requires some configuration so that it works as you want, or you start from scratch and create something specifically for your own usecase.

        In the latter case, it may actually mean a significant amount of development orders of magnitude more than looking up how to configure stuff, constant maintainance, etc.

        • lenkite a day ago

          In Go, people will write code to use the standard library for the app they are developing instead of pulling in a framework to do the work for them. Most Go developers have a culture of minimizing dependencies to utterly essential ones that they cannot write on their own.

          In Java, people will pull in a 100MB+ mega-framework for a hello-world REST service. Oh and another 50MB for ORM. Another 25MB+ for nailpolish, etc.

          The extreme difference in basic developer culture causes visible differences in performance outcomes. Can't even blame the JVM - it is a superb beast that is overloaded by Java developers putting Mount Everest atop it.

          • okeuro49 a day ago

            > In Java, people will pull in a 300MB+ mega-framework for a hello-world REST service. Oh and another 200MB for ORM. Another 250MB+ for nailpolish, etc.

            I just now used https://start.spring.io/ to generate a project using Spring web, Spring security and Spring data JPA (Hibernate).

            It generated a JAR that is 52MB.

            • lenkite a day ago

              Thanks - I haven't used the Spring Generator for several years now. However, I think one also needs to include the drivers, oauth stuff, template libraries, etc to get an accurate represention of "standard Java enterprise size". Gonna play with this offline and see how good it has got.

              • ivan_gammel a day ago

                A real monolithic app dealing with videostreaming that I have been working recently, was based on Spring Boot and AWS SDK and it was a 82 Mb jar file. It had the drivers, oauth stuff, a couple of template engines for business reasons (Handlebars and Thymeleaf), database and queue drivers etc. It could be maintained and extended by a junior developer, because it had established design patterns and they only needed to follow some project conventions. We had multiple releases per week at engineering cost of less than 25k€ per year. I would not be able to build something like that with that budget on Go.

              • gf000 a day ago

                You might want to check the native spring native plugin as well, which can AOT compile the whole thing to a single binary (thanks to Graal).

            • geodel a day ago

              Good. Just a little bigger than linux image size for containers and thats without including JVM.

          • gf000 a day ago

            Most projects won't stay a hello-world REST service, there would be no point of doing them. They will grow and most likely make use of a bunch of CRUD features, on which we have a lots of experience in various languages and frameworks can solve a good chunk of any problem that might come up (AuthN/Z, session management, endpoints, safe parsing from and to json/url/forms, etc).

            Spring (besides itself being modular, so you only "pay" for what you use) will solve all of that for me, so I only have to write the small amount of business-relevant code and be on my way. Later on, some other developer who knows spring can join the project and feel ready at home.

            Compare it to a buggy, slow to develop, slow to onramp home-grown half-solution, and it's quite a clear tradeoff, unless there are very specific requirements that make the usage of frameworks a no-go.

        • whstl a day ago

          > Like, what other option is there?

          For this specific case there's plenty...

          You can use the battle-tested libraries wrapped by Spring directly. For OAuth specifically, Spring does very little.

          You can use other frameworks that also have those features, in Java or in other languages.

          You can use a paid authentication services.

          You can use an open source authentication services.

          • okeuro49 a day ago

            > You can use the battle-tested libraries wrapped by Spring directly. For OAuth specifically, Spring does very little.

            Then you have to work to make the libraries all work together. And deal with updates. Spring Boot allows to to update all libraries together, and know that they work together.

            • whstl 15 hours ago

              For this you setup tests and a CI, which is basic stuff that you can't really skip with Spring.

              If you don't want or know how to do this, then there are all the other solutions.

              Either way: authentication in a Spring app is the definition of "reinventing the wheel".

          • gf000 a day ago

            I was talking more abstractly, in that understanding a given feature to be able to configure it properly is not optional (besides asking someone else to handle some part of the complexity e.g. third party authentication services in this case).

            • whstl 14 hours ago

              But in none of those you "end up spending time messing around with config files and annotations", which was the problem mentioned by the grandparent.

              So yes: there are other options.

      • mike_hearn a day ago

        Maybe? Depends on the framework. I've been using some Micronaut lately and it's a Spring-inspired framework where a lot of stuff Spring does at runtime is done up front at compile time.

        The result is apps start really fast, can be compiled to a standalone native binary with GraalVM, use little memory, and errors that would once have resulted in a complex exception at startup now yield reasonable compiler errors instead (it has compiler plugins to make this work well).

        I can't say I've spent much time messing with annotations or config files in this project. Certainly, what little time has been spent on the framework is more than saved by what it does.

      • javanonymous 20 hours ago

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

        I use Spring Boot at my day job and write mostly web services. I don't spend time messing around with config files and annotations. When I create a service class, I annotate it with @Service, and that is mostly what I need.

        Example:

           @Service
           public record ItemsService(ItemsRepository repo) {
        
              public void doStuff(String country) {
                 var items = repo.findByCountry(country);
                 // do stuff with items
                 
              }
           }
        
        Later versions of Spring Boot has reduced a lot of the annotations necessary, like @Inject if you use constructors etc. There are of course other annotations and configurations, but 90% of what I do is similar to the example I gave above. Things may have changed since last you used it, but the amount of "magic" and annotations is often much less than what is posted in these types of discussions.
    • whstl a day 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.

    • smrtinsert a day 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.

    • optician_owl a day 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.

      • okeuro49 a day ago

        > I tend to say that ultra-framework kills people's expertise and in the long term hardly saves resources.

        You can use as much of Spring or as little as you want. Don't want Hibernate? Use JDBC template.

        I have noticed that people who don't use a framework, just end up inventing their own bespoke framework, which unlike Spring, is not documented and has no help available online.

        • nprateem a day ago

          It's a paradox as old as time itself.

          Otherwise intelligent devs assume they can do a better job without all the "complication" and "bloat", but then just end up with homegrown unmaintainable crap that does half of what the frameworks offer for significantly more effort.

          It's either stupidity or arrogance.

          • gorjusborg a day ago

            Why not both?

            The trouble now it is that Spring Boot allows getting things up and running without having to know anything about what is underneath.

            That is great, until you have to change the way it behaves.

          • optician_owl a day ago

            > all the "complication" and "bloat", but then just end up with homegrown unmaintainable crap that does half of what the frameworks offer for significantly more effort.

            I don't see how you deduct the conclusion of reinventing wheels is the only solution of overcomplex and far from ideal frameworks. But you can categorise this deduction also.

          • geodel a day ago

            Agree. I have seen same thing where many people maintain a home grown crap called kitchen and think they can do better job than getting ready meal from restaurant.

            Developers should not be writing code. Period.

            Stupidity and arrogance is ruling everywhere.

          • okeuro49 a day ago

            So many times I have had to deal with custom "frameworks". I've become jaded.

            At least with Spring, you only have to learn it once.

    • cies 11 hours 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.

  • dominicrose a day 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 day 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 day 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 day ago

      Why would you expect any speedup for programs that run for a split of a second?

  • boreas7878 a day 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.

  • 725686 a day 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 11 hours 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.

  • zerr a day ago

    Do you suggest to use vanilla Java instead of Spring?

  • itsthecourier a day 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 day 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

bryancoxwell 2 days 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 2 days 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.

    • andreasmetsala a day 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.

      • RussianCow a day ago

        If you're doing "dependency injection" by just passing arguments to functions/modules, you're not really doing dependency injection—you're doing "dependencies" without the "injection" part. I'm not saying that DI necessitates a ton of magic, but you need at least a small framework for specifying dependencies and injecting them into your modules dynamically.

        • simiones a day ago

          "Injection" in "dependency injection" simply refers to getting your dependencies from outside instead of building them yourself.

          Let's take the case of an object which represents a simple CRUD web service that needs to talk to a database to get some data. Here is what it looks like without dependency injection:

            func NewService(databaseHostname, databaseLoginSecret string) (WebService, error) {
              databaseConn, err := databse.NewConn(databaseHostname, databaseLoginSecret)
              if err != nil {
                  return WebService{}, fmt.Errorf("Failed to create database conn for WebService: %w", err)
              }
              return WebService{
                  databaseConn: databaseConn
              }, nil
            }
          
          And here is what it looks like with dependncy injection:

            func NewService(databaseConn DatabaseConn) WebService {
              return WebService{
                  databaseConn: databaseConn
              }
            }
          
          This is the only concept: don't build your own dependencies, get them from outside.

          Ideally then there is a place in your application, possibly in `main()`, where all of the base services are initialized, in the required order, and references are passed between them as needed. This can include "factory" style objects if some of these need to be initialized on-demand.

        • lucumo a day ago

          No, that's wrong.

          DI requires that the deps come from outside, not that it's dynamically created. The opposite is that dependencies are created inside the unit. DI is about which part of the code owns the dependency. With DI it's some parent component, without DI it's the component itself.

          DI with magic can simplify the management of component lifecycles, but it's entirely possible to do it without.

        • gf000 a day ago

          No, you can pass (inject) the necessary dependencies to the constructor of an object at creation time, and then simply use that object instance everywhere. The only thing frameworks do is "solve" the dependency graph and instantiate stuff in the correct order.

          This is also the most common/preferred way Spring et alia implements their "framework-aided" DI, so that you can write unit tests easily without bootstrapping a Spring context (and it is just well-designed vanilla Java code).

        • com2kid a day ago

          Design patterns are independent of the implementation technology.

          OO and virtual functions can be implemented in C by looking up function pointers in a table.

          Reference counting can be done by manually incrementing and decrementing references.

          At the end of the day everything is compiled to assembly and the CPU doesn't care what ideology was in the programmer's head, except however much a given paradigm abstracts too far away from the underlying machine.

        • cryptos a day ago

          But that "framework" could be a simple factory function.

    • mukunda_johnson 2 days 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 2 days 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.

      • mavelikara a day ago

        This isn’t anything special about Java. A determined programmer can write Fortran in any language.

    • saturn_vk a day 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.

      • unscaled a day ago

        Too many people confuse the concept of DI with a DI framework. You don't even need a DI framework to write straightforward programs in Java. After all, Java also has interfaces!

        One of the reason people needed a DI framework in Java is crazy "enterprise" configurability requirements and Java EE-based standards that required you to implement a class with a default no-argument constructor. If you're using a web framework like Jooby, Http4k, Ktor or Vert.x, you do not need a DI framework (source: we've written many modern Kotlin applications without a DI framework and we've had zero issues with that).

        Of course, all of our non-toy Go applications are using dependency injection as well. Unless the code reviewer messes up, we won't let anyone configure behavior through globals and singletons.

        • mexicocitinluez a day ago

          > One of the reason people needed a DI framework in Java is crazy "enterprise" configurability requirements a

          No, it's so that you can have something else manage the lifetime and disposal of your services instead of doing this yourself. You don't have to be writing crazy enterprisey code to have the need for this.

          I agree DI is simple, but 100% disagree that you can achieve this through a hand-rolled library without sinking a ton of wasted time.

    • arnath 2 days 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?

      • MrDarcy 2 days ago

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

      • bcrosby95 2 days ago

        DI frameworks save you from writing trivial code, and it masks dependency insanity. This is why I don't use it even in Java. If the codebase gets to the point where a DI framework is really useful then you've fucked yourself over.

        • unscaled a day ago

          To be fair, traditional Java EE apps often required a DI framework, because you couldn't control the main entry point of the program, and the entry point to your code was a class with a default no-argument constructor.

          This is still insanity, but the insanity comes from Java EE rather than the apps themselves.

    • nine_k 2 days 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.

      • lelanthran 2 days ago

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

        Sounds complicated. I don't use Node much (nor recently, for that matter), but when I write f/end JS I capture the chain of function calls by creating a new exception (or using whatever exception was thrown), and sending the `.stack` field to a globally-scope function which can then do whatever it wants with it.

        Will that not work in Node?

        • galaxyLogic a day ago

          I believe it does. Creating a new Error-instance to learn what the current stack is a bit hacky, but it can be useful, even when there is no error. The code can reflect on who is calling it.

          Sometimes a function or method is called very many times so trying to log them all is useless. But at the same time it can be the case that there are multiple callers of the said function. Then looking at a stack we can log just the case of some specific call-chain calling that function.

    • eximius a day 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 day 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

      • Capricorn2481 a day ago

        > Because it just sounds like a manual version of stacktraces

        Because it is. I don't understand it either.

    • Cthulhu_ a day 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.

      • gf000 a day ago

        I would argue that in the majority of the cases that's the only reasonable behavior. I do agree that errors should be handled as close as possible to where they occured, but not any closer -- e.g. there is not much you can do within a library's functions if an unexpected error occured inside. It should be handled at whatever business code happens to call it. And in the worst case, they should bubble up to a central handler, that either outputs an 500 error, or an error dialog.

        Exceptions pretty much make this the sane/default behavior, with rust-like ADTs with syntax sugar being close.

    • fweimer a day 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?

      • Cthulhu_ a day ago

        Go itself doesn't do anything about that, nor does Java or JS dictate anything about using middleware.

        I can't speak for the ecosystem / developers though; there are plenty of examples where e.g. HTTP request handlers are wrapped in several onion layers of middleware itself. But, there's also an emphasis on keeping things simple and lightweight.

        What kind of middleware are you thinking of when you mention things getting bogged down?

    • saturn_vk a day 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`

    • mexicocitinluez a day 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.

    • akoboldfrying a day 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

    • nprateem a day ago

      > Regarding stack traces, it turns out you don’t need them

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

  • cflewis 2 days 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 2 days 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 2 days 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.

      • CharlieDigital a day ago

        Kinda disagree on C#

        It's actually converging with JS and TS

        Small repo: https://github.com/CharlieDigital/js-ts-csharp

        Edit: but I don't want to dismiss either because C# does cover a large gamut of use cases (desktop, 3D/gaming, COM interop, etc.) and it is true that in some use cases, you may see a wider range of keyword soup compared to just building web APIs (where I think it really shines; this is a .NET web API in 4 lines: https://imgur.com/a/simple-net-api-with-route-param-validati...).

      • zigzag312 a day ago

        > C# is approaching C++ levels of syntax indecipherability.

        Can you post an example of such new syntax? In my experience C#'s syntax is getting cleaner.

      • gf000 a day ago

        C# tends to accumulate a lot of new features, but Java has a bog-standard syntax and even new features are very carefully added and easily guessable even if you haven't seen them before (e.g. switch expressions vs statements).

        There is absolutely nothing more readable in Go than in Java, it's just classis familiarity (referencing the simple vs easy talk), I would even wager that too little expressivity will actively hinder code understanding.

        (It's much easier to understand the intent of a complex stream API "pipe" than 4 nested for loops with ifs, with a bunch of dumb if errs pretending to be error handling).

        Also, you are comparing a complex framework that solves 70% of your problem domain (at the price of a bit of a learning curve) with a vanilla Go project that only calls libraries. The exact same is more than possible with Java, it just doesn't make much sense in case of CRUD apps because better ways exist.

      • spicyusername a day ago

        Gotta disagree for C#.

        It definitely has more language features than Go, but by no means is indecipherable.

        I find C# syntax to be quite crisp.

    • zdragnar 2 days 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.

      • Cthulhu_ a day ago

        Exactly; if you ask about for example a test assertion or mocking library like in Java, you get told to not use it. These libraries often add a DSL of sorts, meaning you have to relearn them.

        Granted, Go's testing library / setup is also a bit of a jump from regular Go code (especially things like table tests). But the assertions are just `if`s.

      • demi56 2 days ago

        So the community influences the language, not the language that influence the community that explains everything

        • rob74 a day ago

          Well, it's a bit of both. Java is of course very influential, and because it uses a framework for web applications, many languages/communities started imitating that (Ruby/Rails, PHP/Laravel, JS/[Framework_of_the_day] etc.). Then Go came along with its back-to-basics approach and its standard library which is "batteries included" but definitely not a framework, and for some this is a breath of fresh air, while for others it's apparently unbearably alien and backwards...

          • gf000 a day ago

            I'm fairly sure that most of these frameworks are not direct imitations of what Java did, and there was a back-and-fort co-evolution where standard CRUD web applications can be written in a very very productive way (for the small price of learning a framework).

            Sure, reinventing the wheel is fun, but if I can finish a whole project while the equivalent go code can finally return "Hello world", then I'm not sure it's a worthwhile tradeoff. Java is not anemic when it comes to its standard library, people moved to frameworks because a good deal of CRUD apps have to solve some of the same problems. Don't forget, the only silver bullet is reusing existing code.

          • demi56 a day ago

            I would say the leaders(could be the creators) of the language plays the most important role in its infancy the decisions they make defines what decisions the community are gonna make and the was what Go made right, Java been a business oriented decisions made by the community will also be business oriented

    • nprateem a day ago

      Try understanding what spring boot is doing. Annotation soup. It's practically impossible for a beginner to Spring to debug.

      • mattgreenrocks a day ago

        IntelliJ seems able to locate all potential implementations at injection points just fine.

      • gf000 a day ago

        I mean, would you be able to pilot an airplane at first try? Does it mean that it is badly designed?

        Frameworks reverse who calls who, it is your code that will be called at certain points, not the other way around. Obviously, one has to learn a bit about the framework before touching one, blindly copy-pasting from stackoverflow/chatgpt is not software develolment.

    • rapsey a day ago

      Programs written in those languages tend to have many layers of abstractions, on top of abstraction heavy frameworks.

  • roncesvalles a day 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 day ago

      So, write python in go instead, gotcha.

      • roncesvalles a day ago

        The idiomatic way to write Go is as naively as possible after you fully understand how it works. Otherwise it'll just feel like Java with shitty ergonomics.

        If you're ever writing Go and wish you had real classes instead of this deconstructed "mess" with struct types, methods, and interfaces, you're writing Go totally wrong.

        • unscaled a day ago

          That is certainly one way to write Go, but I wouldn't call it "idiomatic" when evil the Google Best Practices guide recommends against it[1][2]. I'll wager you'll probably find it in most other Go style guides. This is how the Go standard library itself works. While you can use http.Get() if you want to use the default HTTP client for simple apps, the library provides you the http.Client struct, where you can also override the Transport (RoundTripper interface) and Jar (CookieJar interfaces). The transport interface lets you further override the Dialer and Proxy function.

          [1] https://google.github.io/styleguide/go/best-practices#global... [2] https://google.github.io/styleguide/go/best-practices#provid...

          • roncesvalles a day ago

            The example they've chosen is somewhat contrived in that the dependency is maintaining a collection of plugins. In such a case it makes sense to go with their approach. I'm not actually arguing against having multiple instances of some package's functionality; indeed such instances may be created and stored per controller. I'm more so arguing against IoC style Dependency Injection, i.e. central instantiation of the controller and its dependencies. My point is just, as far as possible, it's better to rely on init() and package import relationships. You can simply call dep.New() in any number of inits.

        • gf000 a day ago

          Sounds like a no true Scotsman fallacy.

    • stpedgwdgfhgdd a day 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?

      • roncesvalles a day ago

        Such an instance would just be a variable somewhere that would be initialized in some init() call when the program starts. Every user of that instance will use it directly (as opposed to storing a reference to it locally DI style).

        • gf000 a day ago

          So global mutable state? How wonderfully modern!

    • the_gipsy a day ago

      > Test using monkey-patching.

      Can you elaborate? What library do you use?

    • 3uler a day 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

      • 65a a day ago

        I really cannot say Uber's use of Go is particularly idiomatic to me, having started writing Go more than a decade ago now. It just strikes me as overwrought, and I've worked on big services.

      • Cthulhu_ a day ago

        Just because a known company uses it doesn't mean it's authoritative; there's likewise functional programming libraries built by big companies, but FP should also be avoided in Go because it's not a functional language and not optimized for it.

      • roncesvalles a day ago

        I disagree with the premise of that whole project. It shouldn't exist.

    • akoboldfrying a day 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?

      • tpm a day ago

        Java doesn't really enable monkey-patching in the style of Javascript, Perl etc. AFAIK, or am I missing something? That you can create anonymous subclasses during runtime is different to e.g. editing methods of existing objects/classes during runtime.

        • fweimer a day ago

          JVMTI allows method patching: https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.h... The capabilities are somewhat limited, and an enhancement JEP was withdrawn: https://openjdk.org/jeps/159

          On the other other, there is a movement to remove such late binding/rebinding features from the Java platform. However, I think JVMTI is expected to remain supported if the agents are loaded ahead of time. Only on-demand loading of agents will be removed from OpenJDK.

          • gf000 a day ago

            I wouldn't say that there is a movement to remove such late binding features -- the "movement's" primary goal is simply to properly encapsulate and mark any such part of the code (e.g. make it explicit in the module that that should be supported).

            That way AOT compilation becomes possible/efficient and dependencies can't hack into other packages in an unmaintainable way without explicit permission from the user, so that updating the JVM and dependencies will be even more streamlined.

            • fweimer a day ago

              As far as I know, there is no tooling support to propagate these marks to the launcher program even in cases where they can be statically discovered. (This could be similar how dynamic linker agents (audit modules) are handled by the Solaris linker, via DT_AUDIT and DT_DEPAUDIT markers.) Mainly because OpenJDK does not provide a tool (beyond jar) that creates launchers.

              This doesn't concern features that have been traditionally abused (such as reflection on core OpenJDK classes), but also harmless (from an encapsulation perspective) uses of JNI, and access to future features such as FFI/Panama. Justification for restricting those as well is not so much OpenJDK updates, I think, but that it could cause crashes that might be blamed on OpenJDK.

        • akoboldfrying a day ago

          I agree -- I was just trying to guess what OP might have meant by "monkey-patching" when talking about Go (which is TTBOMK statically typed, which precludes what I'd personally call "monkey-patching" at the language level).

          Java does have "agents", which enable arbitrary control of bytecode at class loading time -- bytecode in existing .class files can be transformed, or completely new bytecode generated on-the-fly. But generating bytecode just to replace a class with a test double would be insane.

          From another comment I learned there's also a separate "Tool Interface" that can do this bytecode-replacement job, but again, it's a huge amount of tricksiness and bother for something more easily and clearly accomplished in source code with a boring old anonymous subclass. (Though it won't work if the class is sealed, or final, or your code uses reflection to check the class name, or...)

  • nine_k 2 days 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 day 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 2 days ago

    How are interfaces a replacement or improvement to context? Is it just that they're type safe?

philipwhiuk 2 days 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 2 days 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 day 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 day 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.

      • ivan_gammel 8 hours ago

        This actually makes me wonder if it is possible to preserve post-startup state and then restore it as a way to mitigate long computational stage during startup. I bet it is, maybe we could just serialize the application context and restore it.

      • pylua a day ago

        I’ll have to look into burst cpu usage. Thanks

    • gf000 a day 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).

      • pylua a day ago

        In my game loop I was using optional. When I profiled it the use of optional was one of the slowest points that could be optimized using null checks and ifs.

        There were a lot of other slow areas as well, not where you would expect it.

        • gf000 a day ago

          Sure, Optional is not the most optimal thing to use/do, though depending on how many entities you were operating with, that itself may still be negligible.

          • pylua a day ago

            That’s the pain point - well written java code may not be the most performant depending on the situation.

            It wasn’t just optional but other areas as well.

            • gf000 a day ago

              That's not my experience, though game development is certainly a niche and Java may not be the top choice for that.

              You might sometimes have to reach for SoA-like structures and reference them via indices, at least for the core ECS, but for the rest you can easily use bog-standard Java -- not everything has to be "ultra-fast, specially written java", just certain hot loops.

              • pylua a day ago

                Right. I didn’t started digging into it until I realized the application was clearly sluggish in certain scenarios. As I am doing this for fun I am going to see what kind of speed up I get in rust for this.

    • rootlocus 2 days ago

      Have you tried AoT compilation with graalvm?

      • pylua a day ago

        No, the product has a lot of aop and I figure it would be difficult to make that work.

    • znpy a day 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.

      • conwaytwitty a day ago

        depending on the size of the app, this can go from a few seconds locally, to 60 seconds when running on 1cpu nodes

        there's just so much being done during startup it requires some burst cpu

    • cmrdporcupine 2 days 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.

      • rahen a day ago

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

        • gf000 a day ago

          Is it really an actual issue? How often do you restart instances or scale up horizontally?

          A cheap 10 years old desktop PC can easily handle all the traffic a medium-sized website generates at all times.

          • pylua a day ago

            What do you define as medium size?

            • gf000 a day ago

              Stackoverflow famously ran on a single, although quite beefy server PC for a long time (not any longer, but not for performance reasons AFAIK).

              I think it's a good data point to have to scale your workload to stackoverflow's, and reconsider the hardware costs.

              (Obviously horizontal scaling has its place, but if it's that variably scalable, maybe there are better solutions, e.g. a single bigger instance -- often times even for cheaper)

      • tugberkk a day ago

        why the downvote on this? is it wrong?

    • throwaway2037 2 days 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.
      • tonyhart7 2 days ago

        well if the argument is cheaper, I can argue that JS/python dev would be more cheaper since there are more pool of talent

        • throwaway2037 18 hours ago

          Double agree. If Java was the right choice 10-15 years ago for cheap enterprise apps, then certainly NodeJS is the way to go today. There are heaps of cheap developers and the ecosystem is ginormous.

          • pylua 18 hours ago

            Is it really to the point of challenging java ? Seems like Java is still the way to go here.

  • jillesvangurp a day 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 day 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.

      • jillesvangurp a day ago

        I never had much experience with scala.js. The nice thing with kotlin-js is that it's just kotlin. Thanks to Android, it's actually a good fit for UI development and a lot of the same libraries work in a browser as well. For example, we use koin for dependency injection, kotlinx serialization, ktor-client, coroutines, and a bunch of other stuff. We also use some npms. Stuff like maplibre and tailwind for example.

        As for the compiled code. I spend about as much time looking at generated js as I do examining jvm byte code (none in case you were wondering). It's a compilation target. Yes it's ugly. But who cares? It actually goes through a mininification/uglification step as part of the webpack build. So, yes, it's going to be ugly.

  • throwaway2037 2 days ago

    Yeah, language ecosystems get no love here. Part of me dreads Java, but the developer experience is mostly worse elsewhere.

pjmlp a day 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 day 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 day 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.

      • zipy124 a day ago

        Another re-phrasing of my comment is that perhaps using Go they do not require the strong senior to kick it off.

        Otherwise yes there is always the argument of "they're just using it wrong" etc...

        ultimately: ¯\_(ツ)_/¯

        • ivan_gammel a day ago

          No. Go is worse simply due to lack of established design patterns. Spring (just like Angular) forces to build things in a certain way and has a lot of reasonable defaults. E.g. CSRF protection is enabled by default in Spring Security. CORS configuration is trivial. Magic? Yes. In Go juniors won’t even think it is necessary and may not figure out that they need a 3rd party library for that (or build it correctly).

  • unscaled a day 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

  • linkdd a day 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 day 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.

      • ReflectedImage a day ago

        Golang's standard library looks pretty complete to me and they will probably master it in a month.

        • WJW a day ago

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

        • mike_hearn a day ago

          But people here are comparing a language standard library with a framework like Spring, which doesn't make sense.

          The Java standard library has a web server in it, it has JDBC. You could use those directly. That's comparable to the Go standard library. For real apps people don't do this because they want a lot more, like simplified database access or session management.

          Look at this: https://pkg.go.dev/database/sql

          It strongly resembles JDBC. Doing a database query with that is verbose and error prone. Compare the work needed to do a lookup query and map the results to a data structure with that to (Micronaut syntax, Spring is similar):

              @JdbcRepository(dialect = Dialect.POSTGRES) 
              interface GophersRepository extends CrudRepository<Gopher, String> {}
          
          ... and later ...

              var someGopher = gophersRepository.findById("goo");
          
          Add the username/password/host/port to the config file and that's all you need for db access. Compare to the Go stdlib which wants you to manage drivers, connections, prepared statements, rollbacks, etc. It's a different level of abstraction.
          • pjmlp a day ago

            And it misses something like Swing, which while not perfect, does the job and the best Go can hope for is Fyne, as third party.

            Multithreaded collection types.

            Configuring scheduling algorithms.

            Pluggable services, cryptography algorithms, filesystem.

            Sane way to manage dates, granted the original one was a bit clunky, but way better than parsing strings.

            Sometimes I wonder if folks that criticise Java, and .NET, actually spend any time learning their standard libraries in practice.

      • btreecat a day ago

        This is why I don't buy the FP hype

    • lenkite a day 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.

      • demi56 a day ago

        > As an example, the Go runtime does not honor container resource limits

        That’s no longer true for Go 1.19+

        • lenkite a day ago

          AFAIK, the basic issue is still open at https://github.com/golang/go/issues/33803 and https://github.com/golang/go/issues/59715.

          You still need to use a helper library like https://github.com/KimMachineGun/automemlimit or https://github.com/uber-go/automaxprocs.

          Go 1.19 only had this in its notes for memory changes

          "...includes support for a soft memory limit. This memory limit includes the Go heap and all other memory managed by the runtime, and excludes external memory sources such as mappings of the binary itself, memory managed in other languages, and memory held by the operating system on behalf of the Go program"

          Forgot to add: The JVM does this for you since JDK 17 https://developers.redhat.com/articles/2022/04/19/java-17-wh...

          • demi56 a day ago

            > ...includes support for a soft memory limit. This memory limit includes the Go heap and all other memory managed by the runtime, and excludes external memory sources such as mappings of the binary itself, memory managed in other languages, and memory held by the operating system on behalf of the Go program"

            Of course it’s a runtime setting, it won’t affect other factors but you can’t say it didn’t solved anything “because there’s a GitHub issue open” Then Go runtime was unpredictable because of its ideology “CPU is unlimited but not Memory” and containers are kinda of a dynamic resource allocated but it did solve vast amount of problem dealing with kernel OOM and unpredictable GC cycles

            > You still need to use a helper library like https://github.com/KimMachineGun/automemlimit or https://github.com/uber-go/automaxprocs.

            I would be surprised if the Go team implemented it into the runtime, because some devs would love to have there own way of handling such settings so I don’t see it as an issue

            > Forgot to add: The JVM does this for you since JDK 17 https://developers.redhat.com/articles/2022/04/19/java-17-wh...

            We can’t just compare added features if don’t compare how backwards compatible the language is at that time I don’t know much about Java, but I wouldn’t say the same from upgrading from Go 1.9 to Go 1.19

            • lenkite a day ago

              Sorry, but no - putting the burden on the developer to detect whether they are running in a container or not and then determine and adjust to cgroup settings is far too high an encumbrance on the service developer. This is a demonstrative example of a fundamental responsibility that should always be delegated to the runtime as the default behavior.

              Neither Go 1.19 nor any subsequent version has "solved" this issue.

              • demi56 a day ago

                > Sorry, but no - putting the burden on the developer to detect whether they are running in a container or not and then determine and adjust to cgroup settings is far too high an encumbrance on the service developer.

                Because there’s no one way solution to this problem, the problem isn’t unique to only Go, but every GC language because you’re starving the program if there isn’t sufficient CPU Quota it will all eventually lead to CPU throttling, this isn’t really the problem of Go or any other GC language but at the OS layer, the inherent nature of containers

                Secondly am pretty sure the Linux CFS does not strictly follow the CPU Quota, tho there could be something like a panic or warning, or switching to entirely different memory management just for what ? people who want 10ms ?

          • btreecat a day ago

            Most Jvm shops/devs I know are still on 11

            • lenkite a day ago

              That's the problem isn't it ? The Java of 8+ years ago is always compared to the Rust/Go/Python of today. (I am also guilty of this)

            • gf000 a day ago

              Upgrading from 11 is trivial though and we are at almost 24. The hardest was 8->9, or 9->11.

            • pjmlp a day ago

              And many folks still code in C89, C99, C++98, Python 2, .NET Framework....

              Language is not to blame for unwillingness to move forward.

    • nprateem a day ago

      Because like any shortsighted decision it can come back to bite you later.

  • kitd a day 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 day 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.

  • xxs a day 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.

  • IshKebab a day 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.

  • Ygg2 a day 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 day 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.

owlstuffing 2 days 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 2 days 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 2 days 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 day 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 day 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.

  • throwaway2037 2 days 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 2 days 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 2 days 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 2 days ago

    Which of these languages is declarative? Aren't they both imperative?

    • heluser 2 days 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”.

      • idoubtit a day 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_...

  • golly_ned 2 days 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 2 days 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 2 days 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 2 days 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.

      • johnisgood a day ago

        I don't want ")?)?;" in Go. I prefer readable syntax rather than symbol soup.

        In Rust, there are cases where I have seen at least 8 consecutive symbols. Not a fan of that.

      • qaq 2 days ago

        99% of people who never written go will know what the go version does

        • wavemode 2 days ago

          Then... look it up?

          If someone saw "go funcName()" for the first time, would they know how it worked without looking it up?

          • qaq a day ago

            Prob not but I would still argue go is way easier to read than Rust for someone who does not know either.

        • cowl a day ago

          and 99% of the people will learn the ? shortcut in fraction second. it's just like every other operator ffs. are you dumbfounded everytime you see the channel operators (->) ? noone takes a second thought to them after the first couple of seconds when they encounter them the first time.

      • briankelly 17 hours ago

        This would drive me nuts to write as a Scala dev, but I can see merit to the philosophy. Go basically lowers the ceiling to raise the floor. Meanwhile Scala can let you glimpse the heavens but has no problem showing you the deepest, darkest pits of hell.

      • wwweston a day ago

        > Exceptions are a terrible idea.

        I hear people say this frequently, but don't ever hear people actually state the case.

        My experience is that particularly for web back-end exceptions have been incredibly useful, but I'm interested in the counter view.

      • searealist 2 days ago

        equivalent exceptions:

            let res = bar(foo());
        
        (I think you meant: let res = bar(foo()?)?;
  • bmurphy1976 2 days 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 2 days 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.

      • hedora 2 days ago

        Exceptions are fine if you never catch them. So is calling abort(). (Which is the Unix way to do what you described.)

        If you need to handle errors, you quickly get into extremely complicated control flow that you now have to test:

           // all functions can throw, return nil or not.
           // All require cleanup.
           try {
              a = f();
              b = a.g();
           } catch(e) {
              c = h();
           } finally {
              if a cleanup_a() // can throw 
              if b cleanup_b() // null check doesn’t suffice…
              if c cleanup_c()
           }
        
        Try mapping all the paths through that mess. It’s 6 lines of code. 4 paths can get into the catch block. 8 can get to finally. Finally multiplies that out by some annoying factor.
        • never_inline a day ago

          > So is calling abort(). (Which is the Unix way to do what you described.

          But in gui applications and servers, you will need to catch and report the error in some intermediate boundary, not exit the application. That's where go falls short.

        • predictionfutu a day ago

          bracket pattern

              def bracket[A, T](ctor: () -> A, next: (a: A) -> T): T =
                  val a = ctor();
                  try { return next(a) } finally { a.dispose() }
        • CharlieDigital 2 days ago

          Can you give the equivalent in Go?

          • coder543 a day ago

            I recommend ignoring the other reply you just got. They are clearly building a bad faith argument to try to make Go look terrible while claiming to sing its praises. That is not at all how that would look in Go. The point being made was that the exception-based code has lots of hidden gotchas, and being more explicit makes the control flow more obvious.

            Something like this:

                a, err := f()
                if err != nil {
                    c, err := h()
                    if err != nil {
                        return fmt.Errorf("h failed: %w", err)
                    }
                    cleanupC(c)
                    return fmt.Errorf("f failed: %w", err)
                }
                defer cleanupA(a)
            
                b, err := a.g()
                if err != nil {
                    c, err := h()
                    if err != nil {
                        return fmt.Errorf("h failed: %w", err)
                    }
                    cleanupC(c)
                    return fmt.Errorf("a.g failed: %w", err)
                }
                defer cleanupB(b)
            
                // the rest of the function continues after here
            
            It’s not crazy.

            With Java’s checked exceptions, you at least have the compiler helping you to know (most of) what needs to be handled, compared to languages that just expect you to find out what exceptions explode through guess and check… but some would argue that you should only handle the happy path and let the entire handler die when something goes wrong.

            I generally prefer the control flow that languages like Rust, Go, and Swift use.

            Errors are rarely exceptional. Why should we use exceptions to handle all errors? Most errors are extremely expected, the same as any other value.

            I’m somewhat sympathetic to the Erlang/Elixir philosophy of “let it crash”, where you have virtually no error handling in most of your code (from what I understand), but it is a very different set of trade offs.

            • coder543 a day ago

              Or, if you really hate duplication, you could optionally do something like this, where you extract the common error handling into a closure:

                  handleError := func(origErr error, context string) error {
                      c, err := h()
                      if err != nil {
                          return fmt.Errorf("%s: h failed: %w", context, err)
                      }
                      cleanupC(c)
                      return fmt.Errorf("%s: %w", context, origErr)
                  }
              
                  a, err := f()
                  if err != nil {
                      return handleError(err, "f failed")
                  }
                  defer cleanupA(a)
              
                  b, err := a.g()
                  if err != nil {
                      return handleError(err, "a.g failed")
                  }
                  defer cleanupB(b)
              
                  // the rest of the function continues after here
          • ted_dunning a day ago

            [flagged]

            • CharlieDigital a day ago

              This is satire, right?

              • ted_dunning 21 hours ago

                No.

                It is just an explicit rendition of a complex topic.

                Do you have a more economical example that handles all of the corner cases of cleanup routines throwing errors?

      • ratorx 2 days ago

        In Go, b) is really common. Most of my code will annotate a lower error with the context of the operation that was happening. You’ll ideally see errors at the top level like: “failed to process item ‘foo’: unable to open user database at ‘/some/path’: file does not exist” as an example.

        Here, the lowest level IO error (which could be quite unhelpful, because at best it can tell you the name of the file, but not WHY it’s being opened) is wrapped with the exact type of the file being opened (a user database) and why the database is being opened (some part of processing ‘foo’, could even generate better error message here).

        Although this is a bit of work (but in the grand scheme of things, not that much), it generates much better debugging info than a stack trace in a lot of situations, especially for non-transient errors because you can annotate things with method arguments.

        I think the common complaint of ‘if err != nil { return err }’ is generally not the case because well-written Go will usually prepend context to why the operation was being performed.

        • codr7 2 days ago

          It's perfectly possible, and a lot less work, to wrap exceptions on their way up the call stack. The difference is you have to remember it at EVERY SINGLE freaking call site in Go.

        • throwaway2037 2 days ago

              > Most of my code will annotate a lower error with the context of the operation that was happening.
          
          This is easy to solve with chained exceptions to add context.

              > it generates much better debugging info than a stack trace in a lot of situations, especially for non-transient errors because you can annotate things with method arguments.
          
          You cannot add method args to an exception message? I am confused.
          • Mawr a day ago

            If it's easy then why does nobody do it? ;)

            In all exception-based languages I know of, catching an exception is so syntactically heavy that annotating intermediate exceptions is never done:

                try {
                    Foo()
                } catch (err) {
                    throw new Exception("message", err)
                }
            
            One line just turned into four and the call to Foo() is in a nested scope now, ew. At that point even Go is more ergonomic and less verbose:

                err := Foo()
                if err != nil {
                    return fmt.Errorf("dfjsdlfkd %w", err)
                }
            • Capricorn2481 a day ago

              > If it's easy then why does nobody do it? ;)

              People do this all the time with exceptions.

              > One line just turned into four

              The Go version has one line of difference?

              > At that point even Go is more ergonomic and less verbose

              You can't compare it to your Go version because you have to write the error check at every single level, whereas once I throw that exception I can catch it wherever I want. Obviously the Go version will have much more code just around one error.

          • ratorx a day ago

            It is definitely possible with exceptions, but it is not the norm (you can do it yourself, but will a library also do it?) because the norm in Java is to silently pass up exceptions as that is the most ergonomic thing.

            And once you start doing it with exceptions, there’s not much difference in the code you end up writing between errors and exceptions.

            In practice, I’ve found that when I write Go, I end up annotating most error returns, so the benefit of exceptions for me would be minimal.

            • Capricorn2481 a day ago

              > And once you start doing it with exceptions, there’s not much difference in the code you end up writing between errors and exceptions

              The difference is where you want to catch the error, and not doing a bunch of "plumbing code" for intermediate callers that don't need to know about that error.

              > because the norm in Java is to silently pass up exceptions as that is the most ergonomic thing

              Adding args to an exception is completely localized. Adding additional args to an error in Go could mean changing dozens of files.

              Not to mention I can actually make my own exceptions for the problem. They are like enums with data.

              • ratorx a day ago

                > difference is where you want to catch the error

                Catching is pretty similar no? In Java you match by type and in Go, you match by `errors.Is`? I guess the static checking in Java js better, but in terms of code written it is no different.

                > additional args to error in Go could mean changing dozens of files

                Just to be clear, here we are talking about a function that already returns an exception/error and adding args to it? That is also a local change in Go as well. The call site already handle the interface for error, not sure why changing a field or modifying the error message would make a difference.

                Arguably, this type of thing is harder in Java. Adding a new type of exception requires modifying all dependent callers to declare/handle the exception (unless they handle the generic Exception), whereas in Go it is a local only change (except if you need to actually handle the error).

                • Capricorn2481 21 hours ago

                  > Catching is pretty similar no?

                  In Go, you have to "catch" it at every call level.

                  > Just to be clear, here we are talking about a function that already returns an exception/error and adding args to it

                  Yes, but adding an arg doesn't mean modifying the error string, it means adding another piece of data which could be a different type. That's another var, and now every call level has to update to pass along that new var. Unless you change the var from a string to a map, which is a whole different set of headaches.

                  > Arguably, this type of thing is harder in Java. Adding a new type of exception requires modifying all dependent callers to declare/handle the exception

                  Only if they need to handle it. If you just want it to bubble up, that function doesn't even need to know about that error or what args it has. That's not the case in Go. Every function has to know about every error that passes through. It's the difference between changing two files and changing 10 files.

                  • ratorx 21 hours ago

                    > That’s another var

                    By another var, do you mean another return value? That’s not how it works in Go at all. It is possible to do it that way, but that would not be idiomatic.

                    You have a single error returned regardless of how many “errors” you have (> 0). If you need to return a new error and it is a custom struct that includes fields, you just implement Error interface on the struct and return it as the single error return. If you need to add new args on the struct, nothing changes other than the error implementation.

                    Do you want to return 2 errors from the same call site? You have to use something like multierror or a custom struct that includes 2 errors and implement the interface yourself. But the actual thing you return is still a single error.

                    > unless you want to change the var from string to map

                    Errors are not strings. It is an interface. If you want to return a string, you implement the interface (although it is much simpler to create a new error with errors.New). If you want to change it to a map later, you implement the interface on the map. It is transparent to the caller, because errors are dynamically dispatched the majority of the time.

                    > only if they need to handle it

                    Well, every function needs to declare which exceptions it throws, so you will have to modify every function in the call stack if you don’t want to handle it and it is a new type of Exception.

                    > That’s not the case in Go

                    That IS the case in Go. The most common pattern is to return an implementation of the error interface. Nothing changes if the underlying type of error changes except (potentially) the sites that want to handle a specific type of error.

        • CharlieDigital 2 days ago

          I don't see how this is different from exceptions though? Exceptions just make it optional if you want to handle it at that level (and you can).

      • kgeist a day ago

        Having exceptions means that every line in your function is a potential exit point, often without you being aware of it. This can lead to bugs when a non-atomic operation is abruptly terminated, and you might not realize it just by glancing at the code.

        When we were rewriting some code from PHP to Go, I remember that simply thinking about "what to do with err" led me to realize we had a ticking time bomb - one that could explode in production. We had never realized that a certain line of PHP code could potentially throw an exception, and letting it bubble up the stack would have resulted in data corruption. With Go's explicit error handling, this issue became immediately obvious.

        • mike_hearn a day ago

          It's rather the opposite, it helps avoid bugs when something is terminated, e.g. by ensuring that if an exception is thrown it rolls back a database transaction.

          Generally you want to do several classes of things with exceptions in web apps:

          1. Return a 500, possibly rendering a nice error page.

          2. Log why it occurred.

          3. Roll back changes you were making at the time in the database.

          And you might also want to do other things, like propagate the error via tracing, increment monitored metrics and many other things. All these are easily done with exceptions, and will often be done by frameworks for you. In desktop apps you may also want to trigger a crash reporter, display a sorry message to the user, and if the exception was inside something like a button click handler you may even be able to proceed safely too.

          Given the level of bugginess in untested error handling codepaths that crop up in languages without them, exceptions are definitely the way to go.

          • kgeist a day ago

            Not everything can be wrapped in a single transaction, though. The problem in question involved talking to other services. Frameworks wouldn't help out of the box.

        • gf000 a day ago

          Go can abort at any point as well.

          Also, ignoring an error condition (by either forgetting about it, or simply doing the nice and tidy if err dance with no real error handling in place, just a log or whatever) is much worse and can lead to silent data corruption.

          • kgeist a day ago

            >ignoring an error condition (by either forgetting about it

            We rely on linters to catch that, which is pretty easy to implement (no expensive intra-procedural analysis needed, which is the case with exceptions).

            >Go can abort at any point as well.

            Panics, unlike errors, are exceptional situations which generally should not be caught (a programming error, like index out of range). They're usually much rarer than errors.

            • gf000 18 hours ago

              Linters can't know if a case has been properly handled or not. Just because it logs something it may or may not be the proper semantic way to handle that error.

          • Mawr a day ago

            You can abort via panic(), but that is expected to crash the application, which is perfectly fine. It's the act of attempting to catch the abort that is fraught with problems. While in Go that is rare, in languages with exceptions it's normal and expected.

            Ignoring an error condition is possible in Go, but so unlikely that it's not practical to worry about it.

            As an aside, not that it matters, but logging an error is one of the valid ways of handling it, depending on context.

            • gf000 a day ago

              Logging can be a valid handling of error.

              I just don't believe that most errors can be handled locally so besides returning an error (bubbling up), not much can be done. Go makes this part of the happy path, so neither can be easily seen/reasoned about anymore.

              Exceptions do auto bubbling up, while languages with ADTs have more strictness than go, and often have some syntactic sugar/macro to help with the common case of returning the error (rust's ?).

              • kgeist a day ago

                >I just don't believe that most errors can be handled locally so besides returning an error

                Sure, but explicit error handling reminds you that the call may fail, and you may want to handle it in some way (or not - then you bubble it up). With exceptions, it creates the illusion of a simple linear flow.

      • Mawr a day ago

        How can you "let the exception bubble up" when you don't know what "the exception" is nor where it's going to be thrown? The agency you imply here does not exist.

        All you can do is what you've described - catch all exceptions at the top level and log them. It's a valid strategy so long as you don't mind your service going down at 3am because someone didn't realize that the call to Foo() on line 5593 could in fact throw exception Bar.

        Explicit error handling would make it obvious that Foo() can error, allowing the programmer to do whatever's appropriate, say implement a retry loop. Said programmer would then be sound asleep at 3am instead of playing whack-the-exception.

        • CharlieDigital a day ago

          Nothing in languages that use exceptions prevent you for catching the generic exception type and handling it if you can recover/clean up. As I stated, my own rule of thumb (point (d) above) with respect to handling exceptions is "when I can/need to do something". If I can't do anything, then just bubble it up and log.

          Case in point is accessing a remote REST endpoint where I might be throttled or the service might be down. I can do something (retry) so I'll write code that probably looks similar to Go with Err:

              var retries = 0;
          
              do {
                try {
                  result = await makeServiceCall();
                } catch {
                  if (retries == 3) throw;
                  retries++;
                  await Task.Delay(retries * 1000);
                }
              } while (retries < 3)
          
          The exception bubbling mechanism just makes it optional so you determine when you want to stop the bubbling.
        • blindriver a day ago

          No, I disagree with you.

          If you are using Go, you have no idea what the error you're going to receive is, because the code you call could be calling other code that you have no idea about. You might use existing code that gets modified to call into another module and then you're going to get a whole set of errors that you aren't expecting and won't be able to react to.

          What this means is that you have NO way to handle errors except to just error out and bubble up. Because all you can do is look at the error, the best you can do is throw your hands up and say "okay just returning this error." How is this any better than an exception?

      • ted_dunning 2 days ago

        Well, my favorite nightmare with exceptions was code that was littered with

        throw NoopException()

        You tell me what that did.

  • rollulus 2 days 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 2 days 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 2 days 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?
      • metaltyphoon 2 days ago

        It seems like the std library has slowly been adding generic iterators methods. Maybe one day?

      • codr7 2 days ago

        Don't know, every time I try to do anything beyond trivial using Go generics I run into some kind of issue. They haven't been around that long, it takes time for ideas to mature.

        • throwaway2037 18 hours ago

              > anything beyond trivial using Go generics
          
          This is the first complaint that I have heard about Go generics on this board. I believe you. Can you share a specific example? It might spur some interesting discussion.
ed_blackburn a day 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 day 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 day 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 day 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 day 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 day 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 day 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 day ago

      So is DI usually referring to automatic framework-wiring DI? Cause I constantly pass dependencies in as arguments and call it DI.

      • aqueueaqueue 21 hours ago

        DI can be manual DI - passing dependencies. I think the hallmark is lack of "new" in your classes (or probably lack of specific imports in a non OO idiomatic language like JS)

        IoC is more about design than dependency resolution mechanisms.

  • gf000 a day 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.

MarkMarine 2 days ago

I 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 2 days ago

    Disasters can be created in any language, right? Has little to do with Go.

    • matsemann a day ago

      True, but most of the arguments against java in this thread are similarly about bad dev practices and not the language itself.

epolanski 2 days ago

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

  • dgunay 2 days ago

    DI in its most fundamental form is common in Go via interfaces. People just declare that arguments to their functions must implement some interface, and the caller provides something that satisfies it.

    "DI" in the sense of having a more complex purpose-built DI container is less common. They exist, but I don't see them used a lot.

    If your application code has a ton of dependencies, the direct injection/low magic style favored by Go can get kind of boilerplate-y. I'm not going to comment on which style is better.

    • CharlieDigital a day ago

      .NET's DI has constructor injection so I suppose this is similar; you just write your constructor saying "I need an `IDataProvider`" and it can come from DI or direct.

      Question I have for Go though. Without DI, what is the idiomatic way of supplying one of n implementations? An if-else block? Like if I had 5 possible implementations (AzureBlobStore, AwsBlobStore, GcpBlobStore, CFBlobStore, FSBlobStore) and I need to supply 10 dependent services with access to `IBlobStore` what would that code look like?

      In .NET, it would look like:

          // Resolve the concreteImpl in DI
          IBlobStore concreteImpl = switch (config.BlobStoreType) {
              case "Azure": new AzureBlobStore();
              case "Aws": new AwsBlobStore();
              default: new FSBlobStore();
          };
      
          // Register
          services.AddSingleton<IBlobStore>(concreteImpl)
          services.AddScoped<Svc1>();
          services.AddScoped<Svc2>();
      
          // Services just use primary constructor and receive impl
          public class Svc1(IBlobStore store) { }
          public class Svc2(IBlobStore store) { }
      
          // But I can also manually supply (e.g. unit testing)
          var mockBlobStore = new MockBlobStore();
          var svc2 = new Svc2(mockBlobStore);
      • dgunay 18 hours ago

        Yeah it basically is just an if-else or switch block.

            type BlobStore interface { /* methods */ }
        
            // later, when initializing stuff
            var foo BlobStore
            switch {
                case isAzure:
                    foo = AzureBlobStore{}
                default:
                    foo = FSBlobStore{}
            }
            
            svc1 := NewSvc1(foo)
        
            // Constructors are just functions and are totally 
            // convention/arbitrary. If the fields are public you 
            // can instantiate the struct directly.
            svc2 := Svc2{ BlobStore: foo }
  • alexjplant 2 days ago

    > Very ignorant about Go, is dependency injection not a thing there?

    It is but there isn't a heavy emphasis on a DI framework per se. At its core DI is "inversion of control" insofar as an object constructor receives what it needs to do its job as opposed to the constructor instantiating said requirements itself. In my experience Golang devs don't use of a bunch of meta-programming via tags/annotations/decorators to tell a third-party framework what reflect-y magic it has to do at runtime; they typically just abstract everything behind an interface and pass instances of what you need to a New function that instantiates the consumer object. I personally prefer this to Spring-type frameworks because it's more verbose and checked at compile time. This means one can easily follow what's going on instead of having to dig through a bunch of nested stack traces riddled with Aspect4J esoterica to find out why some magical Maven dependency suddenly broke your entire app for no reason at all.

    Then again the last time I had to bootstrap a complex Spring project was in 2014. Since then I've used Spring Boot for simple 3-tier microservices and it worked fine... it's likely improved since then but I still wouldn't use it for anything greenfield after the loop it threw me for.

  • bob1029 2 days ago

    DI at its most fundamental interpretation is the act of passing (injecting) arguments (dependencies) to functions.

    Nothing stopping you from doing function-level "injection" of generic or interface type arguments. In this context, function could include the ctor.

  • MrDarcy 2 days ago

    DI is generally not an explicit thing in Go. As a sibling comment mentioned, there is wire but it simply generates code, it is not a runtime DI framework like Spring has. This is a feature. The code wire generates is the same code you’d normally write, which is what makes it unnecessary for many projects.

    Go has interfaces natively which are implemented in a different way. We never need to say X implements I like we do in other languages. X either satisfies the interface or it doesn’t and the compiler and editor catch this fact early.

    So, you’d simply declare an interface to define the API you described in Go, no need for DI or anything else.

  • tymscar 2 days ago

    Totally agree with you, but one point against doing what you mentioned is that you want to replicate your prod environment as much as possible in CI, to be able to catch bugs

    • epolanski 2 days ago

      From my experience this has been mostly painless, and yes, in CI you can obviously run against environments using the same stack as prod.

      It may help that I'm writing plain old boring ecommerces or internal tools (scrapers, chatbots, backoffices, warehouse management, delivery systems, etc) and not something more complex and low-level like databases or rendering engines.

      • prein 2 days ago

        Interesting idea, but I would worry that developers wouldn’t catch things like slow queries or other performance issues that crop up when using an actual database.

  • gt0 2 days ago

    DI 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 2 days ago

      I worked on Wire right at the beginning, happy to answer any questions about it.

      • badrequest 2 days ago

        No questions, but a heartfelt thank you. I've used wire for years and plan to continue doing so. :)

      • gowk 2 days ago

        Given that the development of Wire seems to have slowed down, do you still recommend using Wire for dependency injection in Go projects? How has your perspective on its design philosophy evolved over time?

        • cflewis 2 days ago

          Can’t comment on the slowdown, I left the team a long time ago.

          The goal at the time I thought was very sound: it was a massive PITA that Go programs that ran on cloud servers (of which I would hazard most of them do) were not Write Once Run Anywhere. It was all the same stuff, right? A blob store. A SQL backend. Etc etc. What we wanted was to say “you write a Go program, here run it on GCP. Then Azure. Why not AliBaba if you’re in China”.

          What I am no longer convinced of today is that anyone is really looking for those greased rails because it’s _so much more complicated_ to run on cloud servers than I envisioned. Surely it was going to get more simple? But it didn’t. Networking, authorization, scaling, Kubernetes. The list goes on and on.

          A fully uneducated guess is that Wire does what people who come to it want it to do, but it’s not attracting new users because the use cases are more constrained than envisaged.

deepsun 2 days ago

Seems 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 2 days ago

    Probably it only uses 100mb of heap but they didn't check or tune the memory manager at all

TheCycoONE 2 days ago

We 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 2 days ago

    I 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

    • cmrdporcupine 2 days ago

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

codr7 2 days ago

I'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

  • evantbyrne 2 days ago

    I've written ORMs for both Java and Go and agree that Java is a wonderful language with a much more complete type system than Go. However, it feels like Java web development teams typically suffer from not having their own Wagtail/Payload/etc and build way too much from scratch. Maybe there is some great open source framework+CMS out there making waves in Java land that I'm unaware of?

  • 38 2 days ago

    [flagged]

    • codr7 2 days ago

      I could tell you why, based on writing a ton of code in both, but I doubt that would lead anywhere.

      • 38 2 days ago

        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.

        • throwaway2037 2 days ago

              > that's mandated by Java that each type needs to be in its own file, ridiculous.
          
          Nested classes?
        • galkk 2 days ago

          > mandated by Java that each type needs to be in its own file, ridiculous.

          Each public type, but in general I agree, this is ridiculous decision.

        • codr7 2 days ago

          I don't see that as a problem at all, just like I don't see header files in C++ as a major problem, there are benefits as well and Java has the best IDEs of any language I've worked in except maybe SmallTalk.

          • johnisgood a day ago

            Let me know when you can work on a medium-sized Java codebase in Emacs or Vim smoothly.

            • codr7 a day ago

              I more or less live in Emacs, but I wouldn't use it for drawing pictures or writing SmallTalk either. The right tool for the job.

          • 38 2 days ago

            Java has the best IDEs, because as a language development is essentially impossible without one. contrast with Go where I can put as many types into one file as I like, and I can (and do) use Go without an IDE, both personally and professionally

            • gf000 a day ago

              What part of Java development is impossible without an IDE?

              It's a stupidly simple language.

              Just because people actually use it and thus IDEs were developed to aid the development process doesn't make it a necessity at all. I have programmed Java countless times from vim without any plugins. The only pain point is imports, which would be similarly painful in any other language ever created.

DeathArrow a day ago

I 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 a day ago

    The 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 a day ago

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

jayd16 a day ago

Is 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?

  • kebsup a day ago

    I'd consider a lot of spring annotations "black magic" , because you can't simply go to definition and see how/what they do.

    • marginalia_nu a day ago

      Spring's annotations arguably are black magic, but while Spring offers DI, DI is not Spring, and honestly something like Guice is a lot easier to follow since it only does annotation-based DI and not a bunch of other stuff.

    • gf000 a day ago

      You... 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 a day ago

        grep for annotations...

        That mentality is how you get death by a thousand cuts.

        Cognitive load matters.

        • gf000 a day ago

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

          • hu3 a day ago

            This is 2025.

            If you can't jump to your own code implementation without having to search for strings, your tech sucks.

            And most of your message doesn't even apply. How would I Google or read the manual for my own code?

            We're talking about different things it seems.

            • pjmlp a day ago

              With a 1980's technology out of Xerox PARC, it is called IDE.

              • hu3 a day ago

                Exactly. Although from the reply I got above it seems to be alien tech to some.

                • gf000 a day ago

                  There was a 'may' in my original comment. It is metaprogramming, so you can't see every usage automatically even with "alien tech" like IDEs, unlike in case of a normal type.

                  Especially that we are not even talking about own code, but third-party annotations with its third-party consumers. Also, grepping is a pretty standard term, it doesn't necessarily mean literal CLI grep, but go on with your advanced tooling as if no one else would be familiar with an IDE.

  • psychoslave a day ago

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

evil-olive 2 days ago

it'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

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

  • paulddraper 2 days 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.

    • evil-olive 2 days ago

      > Go's context can be used as a DI container.

      the WithValue/Value API allows you to treat the context as essentially a map[string]any. [0]

      so if you want to make the definition so expansive as to be meaningless, then yes, map[string]any can be used as a "DI container". and so can a context object, because it exposes a map[string]any-ish API.

      0: though if you read the implementation [1] looking up a value in a context is essentially a linked-list traversal and not a hashtable lookup

      1: https://cs.opensource.google/go/go/+/refs/tags/go1.24.0:src/...

    • tomcam 2 days ago

      I thought it was mostly used to terminate an asynchronous event?

      • paulddraper 2 days ago

        Yeah it can inject a signal for that.

ninetyninenine a day ago

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

DrScientist a day ago

Never 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?

  • tored 9 hours ago

    Dependency 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

  • mrkeen a day ago

    I 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 a day 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.

mrbonner a day ago

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

darkhorse222 a day ago

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

pshirshov a day ago

1) 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 a day ago

    Dagger will give you DI at compile time (build time), via annotation processor feature. (And it seems Quarkus can do it as well?)

blibble 2 days ago

seems 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 2 days ago

    Have 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 2 days ago

    Agreed, 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 2 days ago

      went to springone conference in vegas in 2016. eight years later, there are still no good alternatives :)

      • re-thc 2 days ago

        > eight years later, there are still no good alternatives

        Micronaut? Quarkus? Depends on what you need.

        • mattgreenrocks a day ago

          Quarkus seems pretty good overall. No real issues with it.

          Still more config than I'd like but that comes down to the fact it is more for services than MPA webapps.

      • computerdork 2 days ago

        Sad, spring is killing java

        • bdangubic 2 days ago

          if it is, it is doing a very, very, very bad job of it

          • computerdork a day ago

            Okay, maybe I spoke to strongly, it is very widely used and does have some nice ideas (Spring Boot is awesome). But it's just my opinion that Spring does give a lot of new devs the idea that Java is overly complex. And for me at least, I've only needed to use dependency injection once throughout my years as a dev (worked in many companies, large to small). Had to rename a class in a large microservice and didn't have to do a search and replace to rename all the places it was used.

            But, in my opinion, the cons outweigh the benefits. It adds a lot of complexity for a feature that is rarely used. Am sure if used in a major refactor it'd be nice, but some search and replace is much better than adding an entire layer to your system that in many ways is not strongly typed (auto-wiring), and also adds an entire set of steps to the debugging process. Am actually pretty good at debugging Spring wiring problems, but still don't think you should have to have learned the art of Spring wiring just to debug something relatively simple - how your components are linked together - and all done at runtime.

            To me, if tool builders want to support dependency injection, it should be done at the compiler level, along with the rest of the linking of objects. Yeah, don't think dynamically wiring objects is all that useful for a very large majority of builds... ... thought it over, I think I'd like this actually. A new, dedicated component type where the Java compiler checks your DI. That might be very nice. Yeah, also because having a component type will support using DI where it should be, at the component level, not on every class like it tends to be used.

pseudoramble 2 days ago

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

  • voidfunc 2 days ago

    You can write clean beautiful Java and make it perform beautifully if you're careful with what you pull in for dependencies... but then you're not really using Java to its full potential as an ecosystem for basically building massive line of business apps and services that you can spin up everything from junior to offshore to 30 years of experience developers. The tradeoff for this is performance and it's a thing a lot of companies are willing to trade off on.

    Things get messy the moment you involve large frameworks, reflection, ten different approaches to parallelism etc. There's also the problem of code base evolution in Java. Code before JDK-8 feels different than code after it and there's a lot of Pre-JDK 8 code out there in the wild.

    And that's just it at the end of the day... it's a cultural difference in the ecosystems.

  • jeroenhd a day ago

    In my experience, if you write Java like you'd write to, you'd probably get similar performance. Maybe AOT performs a bit better, maybe it doesn't because it disables live optimizations.

    Java doesn't lend itself well to writing Go style code, though. You quickly end up writing very "Java" Java code. That has some advantages (code deduplication, less boilerplate) but also downsides (allocation overhead, performance impacts).

    In terms of Java vs C#, I don't think you'll notice too many new issues. If you opt into using heavy frameworks like Spring Boot to solve annoying problems for you, you'll see RAM usage increase massively, but it'll also take care of a lot of annoying grunt work for you. Startup times overall are pretty similar. The biggest downside in my experience is the lack of nullable types (@Nullable annotations are a poor substitute) and some syntax improvements that are new to Java but have been part of stable C# for many years.

    As long as you stick with small libraries and avoid writing too much ObjectInjectorFactoryProducerResolver style code, performance of the JVM is fine.

    All of that goes as long as you're able to use modern toolkits. If you're stuck in the world of Enterprise Java with Oracle Java 8, you'll notice old Java's shortcomings a lot more.

  • gred 2 days ago

    My experience with very simple Jersey web apps is ~1.5 seconds to start up. Much less than his reported ~8 seconds with Spring Boot, but still not in the 100 ms range he reports with Go. I assume one second or so is about as low as you can go with a mainstream Java framework without AOT, though I'd be happy to be corrected.

    • maccard 2 days ago

      I worked on an app in kotlin a while back, and am currently working in a dotnet app. We can run our entire unit test suite faster than the JVM started up on that project.

      Also, 8 seconds is quick in my experience for Java - I’ve seen more like 15-30

      • winrid 2 days ago

        The JVM starts in milliseconds

        Probably you're loading many thousands of classes...

        • mrkeen a day ago

          It certainly tells me it does. i can hit 'run unit test', sit back in my chair and zone out for 5 seconds, then come back and read that the test took 22ms.

          • PhilipRoman a day ago

            Thanks for bringing up some painful memories. Now I'm working in C and tests take about 100ms end-to-end (multicore, no frameworks). Sometimes I re-run them for no reason just to enjoy how fast everything is.

          • winrid a day ago

            haha well your test did take 22ms, but starting the JVM and loading all the classes and all your dependency injection stuff probably took 5s. You can test it yourself with a tiny hello world, it's pretty fast to start.

            • maccard a day ago

              But that's our point - you're saying java is fast (and it is ripping fast once it gets going) and the startup is fast, unless it's not.

              • winrid a day ago

                Well no, startup is fast, period. You're probably just giving the class loader a ton of work on startup? I would start with checking that I think.

                It could also be when he's hitting "run test" it's actually "compile and run"...

                • maccard a day ago

                  > Well no, startup is fast, period. You're probably just giving the class loader a ton of work on startup? I would start with checking that I think.

                  Ok, maybe I misspoke about the "JVM" startup. But the time between `mvn test` and it actually logging the first line of user code was in the region of 15 seconds. Every java project I've worked on has had startup time issues once they're bigger than a toy project.

                  Saying "the JVm startup is fine, it's just the class loader that's slow" reinforces the point of "it's slow to startup"

                  > It could also be when he's hitting "run test" it's actually "compile and run"...

                  This would be true in C# too, and in go. Both of those tools have much quicker compilation times IME than java, which is just a nice plus.

                  • winrid 13 hours ago

                    BTW, I wonder if any test frameworks are using the hot reloading JVMs yet? Would also solve this problem.

                  • winrid 18 hours ago

                    That's mostly because nobody cares enough to optimize it, but if you split code into modules so your number of classes, including dependencies, is smaller, then your tests will start quickly. Usually it's dependencies and DI that's the cause of this I think.

                    Aren't Go's compile times about the same as Java's? Of course it depends on what tooling you use to package the jars.

                • mrkeen a day ago

                  Well I could switch to a programming language that isn't so slowed down by ahead-of-time compilation. Maybe a JIT language?

          • maccard a day ago

            Yeah that’s been my experience too.

    • matsemann a day ago

      Also, you don't have to continuously restart the server for every change. Can hot swap code. And for tests there are solutions to keep the jvm running between the code->test->fix cycle. But I've never felt the need for those, running a single test is often plenty fast as one doesn't need to instantiate the whole app, only the dependencies needed for the test. So plenty fast.

    • p2detar 2 days ago

      Can confirm with Vert.x. I used to have a JRebel subscription for code hot swapping in another project but in my current Vert.x thing I simply don’t need it. It’s just fast.

  • xxs 2 days ago

    Spring 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 2 days 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 2 days 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%)

        • jamesfinlayson 2 days ago

          Ah okay - yeah I'm not across the internals at all.

          And yeah I assume it's a single core, but definitely a heavy start.

      • trallnag a day ago

        This makes recycling nodes, be it ECS or Kubernetes nodes, that run tons of Spring apps, a huge pain.

  • nelup20 2 days ago

    You can do AOT compilation with GraalVM to reduce both startup times and memory usage, but then you don't have the JIT.

  • tomohawk 2 days ago

    Having done a lot of Java and Go, Go has much better mechanical sympathy between the language, libraries, and vm than Java does. The JIT GC in Java are marvels of engineering, but they have to be.

    As an example, in Java, everything is a pointer, so pointer chasing all the time, which is not good for cpu cache, etc. In Go, there is first class support for composition.

    The other main adjustment, if coming from Java, is reduced cognitive overhead. It usually only takes a week or two for an experienced Java dev to be reasonably effective in Go, but it takes a few months to break the mental habits of overthinking everything.

    • codr7 2 days ago

      There's nothing forcing you to write EE style code in Java though, or depend on frameworks written in that style.

    • re-thc 2 days ago

      > As an example, in Java, everything is a pointer, so pointer chasing all the time, which is not good for cpu cache, etc

      Strictly speaking that's not true. It's everything is a pointer in theory to make it easier to reason with and JIT / JVM optimizing in the background.

      There are primitive types and there are lots of tricks in the JVM e.g. escape analysis that places objects on the heap/stack etc.

      • PhilipRoman a day ago

        Despite all the advances in JIT, I've literally never seen it correctly optimize a HashMap<Integer> (happy to be proven wrong). Hopefully the renewed focus on value types can finally bring some sanity.

        • neonsunset a day ago

          In C#, all struct generics are monomorphized and struct-based abstractions are zero-cost :)

cs02rm0 a day ago

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

monksy 21 hours ago

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

wiseowise a day ago

I wish this “DI black magic” meme would die. If you’re afraid of runtime DI, then use Dagger 2.

  • KronisLV a day ago

    Never really heard of Dagger before, but I love what I'm seeing: 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!

anthonybsd a day ago

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

Mekoloto a day ago

This 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

rr808 2 days ago

The best thing about Go is no Spring framework. I like Java but finding Java projects without Spring is difficult.

  • jjav 2 days ago

    What 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 2 days ago

      But 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

rzz3 a day ago

A coworker once claimed that Go is the new Java, and I haven’t really been able to refute it.

DeathArrow a day ago

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

newAccount2025 2 days 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.

pm90 2 days ago

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

  • skeeks 2 days ago

    There 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 a day ago

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

raffraffraff a day 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.

yodon 2 days ago

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

  • WuxiFingerHold a day ago

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

    • mattgreenrocks a day ago

      Currently learning ASP.NET Core, seems very well put together with quality first-party libraries. I miss Kotlin, but I suspect ASP.NET Core and Blazor SSR makes up for that.

      Particularly excited by the fact that Blazor SSR lets you write server-side components. So I can have a statically typed language that lets me think about the UI in terms of components, and a DI container on hand for when I want to break business logic out into services. Love the flexibility all of those things coming together affords. EF Core seems really promising for persistence so far, though I need to play with it more.

      Playing with their Identity framework last night I was able to get up and running despite being new to the ecosystem. Very good sign, most of the time integrating auth as a newcomer to a lot of languages ends up being way more than 2 hours worth of work.

  • fullstackchris 2 days ago

    Probably 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 a day ago

    It’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 a day ago

      Go 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 a day ago

        There 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 with the main repositories being runtime, roslyn, fsharp and sdk.

TheSmoke a day ago

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

user9999999999 2 days ago

Any 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 2 days ago

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

zwnow a day ago

There's one simple argument to never touch Java: FactoryFactory produce ArgumentFactory, ArgumentFactory.builder().build(argument)

staticelf a day ago

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

  • switch007 a day ago

    20-30s sadly isn't even that terrible

    • staticelf a day ago

      I'm guessing you're probably right which makes me kind of wonder how java devs get any work done at all.

smrtinsert 2 days ago

I thought we had moved on from flame bait articles as a species

honkycat 2 days ago

After 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 2 days ago

    After 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

    • throwaway894345 2 days ago

      I’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 a day 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). 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.

    • diamondfist25 2 days ago

      Btw js/ts, python, and golang, which one do you find more productive with?

      • scubbo 2 days ago

        Hard 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 a day ago

          Baffling. 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 a day ago

            Agreed! python easily has the worst package management out of all of them!

invalidname 2 days ago

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

  • azaras a day ago

    There are operators written in Java and Rust.

    • claudex a day ago

      There are even operator written with Ansible

anta40 2 days 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. Never try to write something serious with it, just messing around with the examples.

rvz 2 days ago

Agree. 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 2 days 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 2 days ago

      Big 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 2 days ago

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

        I mean you could say the same thing about TypeScript - if your entire stack is TypeScript then there's no need for the type validation either.

        • CharlieDigital 2 days ago

          Even if your entire stack is TS, you still need validation because the submitted data from an external interface (API call) might not be valid.

          You can't just accept any JSON payload and expect it to be valid.

          In Java or .NET, it will fail at serialization when the runtime tries to map it to a static type (automatic). This is not the case at runtime with JS (because at runtime, it's no longer TS). Thus you need a Zod or a Valibot to ensure the incoming payload is actually valid by describing the valid types (even if your whole stack is TS at dev time because schema mismatches are a runtime problem).

          .NETs System.Text.Json does the mapping and validation using either reflection or AOT generated serializers transparently and automatically because it has static types.

          • jbhoot 2 days ago

            I think CharlieDigital's point is that a bad payload will fail right at the serialisation boundary in case of .NET. We know the problem right there. Now we only need to fix the bad payload.

            For TypeScript with only types and without validation, a bad payload gets through, and there is no telling where in the workflow it will explode. This could waste more time and developer resources in debugging.

            • amazingamazing 2 days ago

              Again this isn’t an inherent property of .net, you have to add validation. There are plenty of ways to do this in node so the point is moot.

              • mattgreenrocks a day ago

                The issue is that this sort of validation boilerplate shouldn't have to be written. The framework should be able to figure it out from the HTTP handler declaration. I suspect this is why FastAPI got so popular in the Python world.

                IMO, a lot of the JS world seems mentally fixated on express.js-levels of abstraction still. Anything more is viewed as "magic" and viewed as suspect because it requires learning.

                • CharlieDigital a day ago

                  The irony is that the "learning" just gets applied elsewhere; there are certain foundational building blocks that I think every language and platform needs once you start building "serious" applications.

              • CharlieDigital 2 days ago

                Here is a .NET web API

                    var builder = WebApplication.CreateBuilder(args);
                    var app = builder.Build();
                
                    app.MapGet("/{userId}", (int userId) => userId);
                    app.Run();
                
                See `int userId`? If I call this API with `http://localhost:5123/asdf`, I will get an error because the types don't match. If I call this with `http://localhost:5123/1234` it will work fine. The same would be true if I required a class `User` here. The router is able to introspect the type at runtime to determine if the types match; both basic types like this as well as complex types using the built-in serializer. It is built in.

                I've put it into a short clip for you: https://imgur.com/a/WNbGUQD

                • amazingamazing 2 days ago

                  I’m not sure why you’re so obsessed with this. You can do the same thing with any validation library in nodejs. Your exact example is possible by integrating any validation library of your choosing into a nest js route pipe. In particular primitive type validation is built into nestjs anyway

                  The fact that it’s built in is neat but not really important. Most people are not making thousands of toy apps. If the necessary they will integrate and move on.

                  There are more compelling reasons to use .net than this.

          • amazingamazing 2 days ago

            Yes .NET's built in SDK is larger than Node.JS's. I'm not sure what your point is exactly. As you stated just use a validation library if that's necessary. It generally is not though, you can cast the entire output to a type in TypeScript, of course this isn't validation, but it's generally good enough.

            Of course if you’re writing a full stack app you can share everything, validating the front and backend with the same code. Clearly that would be a strange thing to hold against .net, similar to the lack of built in validation in typescript.

            • CharlieDigital 2 days ago

              You double up your work. What you really wanted all along are static types.

              • amazingamazing 2 days ago

                not necessarily, and in plenty of programming languages static types do not give you validation for free, and in the case of .net you still have to call something else. it's no different than just using zod, other than the fact that it's built in.

                if you're using typescript in the front end, seems double to even bother to introduce .net, when typescript and node is fine for the backend too.

                • CharlieDigital 2 days ago

                      > [amazingamazing 31 minutes ago] I mean you could say the same thing about TypeScript - if your entire stack is TypeScript then there's no need for the type validation either.
                  
                  I think that if you're here trying to make the case that if your whole stack is TS, you don't need to validate your incoming data, then you probably haven't built a system of consequence where the data actually matters and none of this discussion really matters; you have no context. Go ahead and try to `curl` some nonsense data to any of your TS endpoints and see what happens at runtime without validation.
                  • amazingamazing 2 days ago

                    I'm not really sure what your point is. you can add validation in node.js, just like you can with .net.

                    also, with curl you'd use isomorphic type safety, or just know what you're expecting and hard code the types per inputs, trivial with typescript.

                    anyway, I'm done here since this conversation is going in circles.

    • bdhcuidbebe 2 days ago

      I guess you just dont know the domain well enough to understand the kind of issues strong typing and compiled software is solving.

      • amazingamazing 2 days ago

        Please do tell, what typescript, node and a few dependencies cannot do here.

        • bdhcuidbebe a day ago

          Provide strong typing, low memory footprint and good performance at scale. See the topic of discussion for more details.

          • amazingamazing a day ago

            Yes, I agree, but what you're saying isn't really relevant in an I/O bound scenario. In any case, I wouldn't write a K8s operator in node.js, yes. Go is the best for that.

            • bdhcuidbebe a day ago

              Sorry, but what IO bound scenario?

countrypao a day ago

[flagged]

  • teruakohatu a day ago

    Was this summary written by an LLM?

    • countrypao a day ago

      You got it, this summary was based on my fragmented thoughts, which I then refined and structured with the help of an LLM.