Hacker News new | past | comments | ask | show | jobs | submit login
Java 21 makes me like Java again (wscp.dev)
412 points by wscp-dev on Sept 16, 2023 | hide | past | favorite | 742 comments



The biggest feature in Java 21 is the release of Virtual Threads: https://openjdk.org/jeps/444

For some reason, this is missing from the article. If there was any feature that would sway existing Golang developers to switch to Java, it would be this. It would perhaps also convince the haters of the reactive-style concurrency patterns.


I don't think any existing Go developer is going back to Java.

I worked with Java for 10 years and switched to Go and I will never go back.

This is mostly because applications and libraries are so hard to reason about and understand due to inheritance, packaging, OOP, build tools ect compared to Go.

Go is simple. It's easy to understand, read, and maintain. The packaging is like how you would package files on your computer in single folders. The tooling is built into the language. You don't need a IDE like IntelliJ just to make it feel reasonable to work with.

Maybe all of this has changed, but most of the libraries I see in Java today still look like this.


> This is mostly because applications and libraries are so hard to reason about and understand due to inheritance, packaging, OOP, build tools ect compared to Go.

You are just at the early phase of the project.

> Go is simple. It's easy to understand, read, and maintain

My opinion is that Go is too simple, to the point that it hinders understanding and readability. 4 nested loops with random mutability is much worse than a clear Stream api "pipeline". The 31st if err check will not be actually handling the underlying problem, while you can't forget about Exceptions -- even if they do bubble up to the main app, you will clearly get an actionable stacktrace.

> The tooling is built into the language

This has benefits, I will give you that. At the same time, I think all these new build tools are anemic and can't be used for anything more complex than interacting with the same language's standard dependencies. Gradle is not sexy, but is actually capable of doing any kind of build.


Im convinced Go is just an incomplete language masquerading as a simple one.


Go is a complete and simple language, and this is a fairly objective claim.

Unless by "incomplete" you mean it could have more features. But C++ and Rust (and Java) have been piling on features for years, with no signs that they will ever slow down or stop, proving that they're also "incomplete" by this definition. IMO this is really just a result of there being too many cooks in the kitchen, and a lack of leadership committed to saying "no" to feature requests.

This is also why using Rust or C++ requires every organization to agree on a subset of the language they will utilize. Rust isn't as far along this path, but it's heading in the same direction as C++.

But with Go, every organization can use the complete language. That's how simple it is.

Go has been used by many organizations to build stable, large, and scalable systems that have been operating successfully in production for many years now. That's how complete it is.


I wonder if replacing the type system in Go with the one in Rust and adding native Actor model support would be doable. Or if GC and compile time would regress too much. Modern languages without good sum types just seems like such a lost opportunity to me.


What would be the point? Why stick with Go if you want so deep-reaching changes?


That what I often wonder, if one is going to start using a new language why go? Rust at least it gave one memory safety and not having to use GC make it more stable usage of resource as the discord article have already pointed out. Seem like go is just google's .NET, every big corp need to have their own language.


>>You are just at the early phase of the project.

Nah.

99% of the code written out there doesn't need layers of indirection, responsibility tossing around, Code splitting across classes, design patterns, inheritance and the class jungle that is common in Java.

Rise of languages like Go is simply majority of the people realising when they want X, they are better of writing just X. You don't have to write a generic version of X that needs to work in a dozen situations. This is for a simple reason. Most of the times, there are no dozen situations. Most, not all the times.

Most of the code I write, doesn't change all that much. If you are writing code that needs to run for decades in an industry where grifting and job hunting is a daily affair you are doing it wrong.


> Most of the times, there are no dozen situations.

After 20 years of building all sorts of software, I know that this a fact, but the challenge is to make others aware of it.


> You are just at the early phase of the project.

Lately, I am seeing very few codebases getting supported more than 3 years in companies offering software products. Every 2-4 years services are getting rewritten, what's the point of having tool intended for 15 years, when services are deprecated in 4 years


It would be good for my job if learned go, but I've been having a hard time because it's just so boring.


You're doing it wrong if you think programming for work is about the code being a means of expressing your inner soul. The most successful professional programmers derive their enjoyment from achieving business objectives, like making products that users love and improve the world.

If you want to express your soul in your code, do that on the weekend using whatever language you want. Trying to mix these motivations is a recipe for disaster.


> The most successful professional programmers derive their enjoyment from achieving business objectives, like making products that users love and improve the world.

Successful by what metric? Achieving business objectives? I don't necessarily disagree with the point, but this seems like a truism.


Or a tautology. No true professional programmer would derive more enjoyment from beauty than from making his boss happy.


I have too much skepticism of "businesses objectives" to be especially interested in success. I want something that'll bend my mind.


Honestly, just find yourself a project and just Go for it (sorry for the pun).

The syntax is easy enough you can start coding just after having looked at basic examples.

Personally I actually like Go because it's so boring.

I tend to see languages more like tools rather than valuable knowledge I must learn, so a simple yet powerful enough language is a perfect fit for me.


I did. Did Go professionally for ~5 years and went to JVM.

Go is easy, but it's surface level easy. Building and maintaining large applications in Go if you don't have a huge team is a giant pain.

JVM (especially paired with Kotlin) for me atleast has meant regaining the expressivity I was missing from Ruby and Python when I moved to Go whilst retaining (well actually usually beating) it on performance and scalability.

I lost absolutely nothing to go to JVM but gained so many things.


I’ve got to disagree with this strongly. I built and operated the same large system in both languages (acquisition forced me to rewrite) and both the dev and operating cost of the JVM was much higher.

Go is a high velocity language. Code reviews on language/style issues are non existent, it’s GC is blazing fast (not our experience with JVM) and it’s really easy to read. I’ve watched many new engineers ramp up on both systems, as both operated side by side for a while, Go was inevitably faster for engineers.

What made maintaining Go hard for you?


The main pain-points in a large org at least:

Lack of a "default" stack for nearly anything. The stdlib is great but the ecosystem isn't. Which web framework? Does it come with a logger? If not which logger? Do the third party libraries I want to use work with my logger/have a mechanism to provide one via an interface or am I shit out of luck? This goes for so many more things though, cache libraries, data structures (because Go stdlib collections are a joke). Contrast to Java here you have a relatively minimal set of very long-lived deps for all of these "basic" things. Ecosystems like Spring, the slf4 facade, Apache etc provide the foundation that most Java programs sit on and that has no alternative in Go land.

Go module transition was also hot garbage. It's better after sure but going through it was worse than Java 8-9 or to Java 11+ both of which were "difficult" transitions for Java but vastly less disruptive for me personally at least. Then take all of this stuff and multiply it by the number of teams you have if you don't have a central team doing library choices and laying down architecture guidelines - which we eventually got but not before all the Go codebases had turned into by and large unmaintainable messes.

IMO Go is high velocity only in the simplest sense, it's very easy to pump out shit tons of code. With a big team of mediocre developers this is even more true. The problems all come later. Big change in requirements? Good luck with that. Had a team try to go crazy functional with Go and now they have immutable data everywhere and allocations are completely destroying the throughput of the Go GC? Good luck fixing that. etc.

The velocity eventually moves from it's strength to it's achilles heel once the codebases are big and bad they are really hard to fix.

I get that most of these problems are "big company" problems and maybe in smaller teams with a stronger hiring bar you won't run into these or maybe not at the same severity but they severely impacted my view of how well the language scales to large teams and codebases.


Teasing this apart I see a few things: a) logging, b) modules rollout and c) missing frameworks.

I’ve never had a logging issue in large systems. Explicit error return (as you know, on every function) allows you to log in your code, not lean on only libraries that support your interface.

Modules rollout was part of growing up. But, you won’t get Go 1->2 upgrade issues as we have 100% backward compatibility on version upgrades. Moving to the latest version of Go is trivial and simply unlocks new features.

Too many allocations for Go is going to be too many allocations for JVM too. This seems like an problem isolated to that team.

I’ve done Go at both a three engineer startup and at Google and I can’t help but notice none of these are really the type of problem that crop up later.


Good to hear logging has gotten better.

Though I still don't see a standard web stack. "stdlib is enough" is only true for people that don't need their hand held and/or can organise good standards across teams.

If it was just me or people I know are good writing the software that works. At Google it probably works because of aforementioned hiring bar. At the average large-ish company? Not something I like leaving to chance because I have usually ended up disappointed.


> Too many allocations for Go is going to be too many allocations for JVM too

No, Go’s GC is a toy compared to the JVM’s. It is lowerish in latency by actually stopping the application threads when under high contention.

Java doesn’t slow down the allocation rate, it tries to keep up with the churn.


Even it's latency is outclassed by ZGC and Shanandoah.

The GC monoculture is great from a simplicity and out of the box experience but there is a very good reason to a) want multiple different GCs tuned for different workload types and b) have competition so that the best designs can be found rather than having to fight to be the only implementation.


Huh, Go also does not allocate ten times more more memory like Java. So even if Go's GC is 1/10th performant as Java (and it isn't) it will be equally better Go applications.

Java's GC improvement is relentless because Java's applications are relentless in memory allocation.

Being on endless Java memory/perf issue prod calls I can say Java GC improvement, performance tuning is cottage industry in itself. Meanwhile end users keep suffering but at least Java devs get to tune GC to their heart's content.


I’d love to see you elaborate on this. Anecdotally, my experience was the exact opposite. Is there some documentation making this point?

In the course of optimizing I came to know the various JVM GC algos (concurrent mark/sweep, parallel old, etc) by the corresponding memory graph alone. I never, ever had to debug similar latency in the Go stack.


>> (concurrent mark/sweep, parallel old, etc)

Both of those are only picked for low sized heaps with few cores, probably within a container. Were these micro services?

G1 is the default for larger heaps and multiple cores, and ZGC and Shenandoah (low latency GCs) have to be manually turned on AFAIK.

OP said:

>Java doesn’t slow down the allocation rate, it tries to keep up with the churn.

This is incorrect. ZGC will block a thread when it cannot give a thread any memory, because it can't collect and free memory at the pace needed. Google "allocation stall" for this. ZGC can achieve very low latencies akin to Go's GC, I don't know if the throughput is higher or not. Multiple cores and some GiB of heap space is when ZGC will shine.


No web framework, the standard library is enough. log/slog is in the standard library now, and is compatible with zap, which seems to work with everything.

>IMO Go is high velocity only in the simplest sense, it's very easy to pump out shit tons of code. With a big team of mediocre developers this is even more true. The problems all come later. Big change in requirements? Good luck with that. Had a team try to go crazy functional with Go and now they have immutable data everywhere and allocations are completely destroying the throughput of the Go GC? Good luck fixing that. etc.

I doublt things would go better for teams trying to go full OO in haskell, or full functional in Java.


The stdlib really isn't enough unless you want num_teams x session handling implementations etc.

> I doublt things would go better for teams trying to go full OO in haskell, or full functional in Java.

That is the beauty of Java, no one does this.

They don't feel like they need, should or will get approval to.

Go is pretty much the wild west because "the std lib is enough" attitude permeates everything. Build your own datastructures, build your own anything really.

I can understand why many people find that attractive and hate Java as a result, sometimes Java feels more like Lego and less like programming but it does create predictable reliably constructed software that is generally easy to clean up even if it's done a bit poorly.

Which is something I value because I often end up in the code janitor role and/or being air-dropped in to get a project back on schedule or drastically cut down the defects etc.


> JVM (especially paired with Kotlin)

I agree the Go lang compiler/runtime needs "a Kotlin", with more null-safey build in, proper sum types, a std lib that makes heavy use of null-safety and sum types, better story for polymorphism, better ergonomics for writing stream pipelines, and additional compilation targets (like JS, WASM). This all while proving stellar interop with all Go code, obviously.

Kotlin is quite a sweet deal.


Curious to understand what you are suggesting here. What’s a better story for polymorphism than duck typing? What would (better?) null-safety look like in Go?


I'm saying that we need an XYZ to Go what Kotlin is to Java. The I name some ways that Kotlin improved upon Java that Go would also benefit from.

> What’s a better story for polymorphism than duck typing?

Something that give compiletime guarantees and has not runtime overhead.

> What would (better?) null-safety look like in Go?

https://kotlinlang.org/docs/java-to-kotlin-nullability-guide...


Going on 12 years with Go and agree with everything.


Go's major selling point to me is that you can ship one binary, nothing beats that.

Python, Node, Java all have to pre-install lots of dependencies before you can use them, fine for developers, not so great if you want to distribute software for people who are not software savvy, who typically just wants to download one file, install and start using it.

c and c++ can also do one executable, but, it is not as portable as Golang, and not as static-link friendly as Golang.


> Go's major selling point to me is that you can ship one binary, nothing beats that.

You can ship one compiled binary in Java too if you want it. https://www.graalvm.org/22.0/reference-manual/native-image/

> Go is simple. It's easy to understand, read, and maintain

Go involves a lot of code repetition which makes it difficult to human-scan and maintain. Worked on both large scale Go and Java projects and I found Java projects easier and more comfortable to maintain. Had to pay a lot more attention to Go. Go is easier to write though thanks to its well-designed and extensive standard library which is possibly the best in the world, but the maintenance angle still tilts to Java. There are also more Gotchas in Go compared to Java.


I’m neither a Golang nor a Java developer per se, but I touch source from both as an SRE. I find it weird to see so many comments saying Go is verbose and Java is not. I found the opposite to be true. To do every little thing, the Java code has a ton of abstraction, and the actual implementation of anything is so far removed from the place where it is used that it gives me such a headache to touch anything without wondering “whatever else would break if I change this behavior?!”

However, the Go code bases I’ve encountered have been less abstract and to the point(even if it means repetition of some little things). I also found it to be free of boilerplate except for the if err != nil part.

I guess those that have spent decades in such abstractions must have learnt a skill to navigate such a spaghetti. But, I still hate it every time.


Quite a bit depends on how the codebase has been written and what era are you looking. Before Java 8/9 or after it. Java code written in the recent era is pretty lean and mean.

No matter the amount of abstraction though, one rarely runs into the sort of issues in Java that Go code tend to run into frequently - causing multi-million dollar mistakes frequently even for experienced Go programmers. For loop semantics https://bugzilla.mozilla.org/show_bug.cgi?id=1619047, Unintended variable shadowing, common mistakes in slice appends/copies, slices and memory leaks, defers inside loops, nil interface vs nil, panics in go-routines. There are loads of bugs in OSS projects wrt to these usually repeated again and again.

Go is very simple to learn and very hard to master writing bug-free code. Looking forward to seeing how languages like Rust perform if/when adopted by enterprise.


> You can ship one compiled binary in Java

Even if you’re shipping a jar. You can ship one artifact by using jlink or you ship the runtime in the docker image.

This has been a solved issue for ages.


> not so great if you want to distribute software for people who are not software savvy, who typically just wants to download one file, install and start using it.

i think it's a flaw that wasn't considered properly in the standard java toolchain to not produce an embedded java runtime into a final packaged artifact that is self-executable.

You end up with third party tooling like: https://www.ej-technologies.com/resources/install4j/help/doc... (and https://launch4j.sourceforge.net/ too).

If oracle bundled this tool into the JDK, it would've not been an issue at all.


It has existed for 20 years, and already started with Sun.

Having AOT compilers as commercial offerings, was seen as one way to capture value in the Java market, in a culture where most compilers were still commercial, GCC being the exception.


>It has existed for 20 years, and already started with Sun.

In an anemic way, with mostly third party offerings few people know or care about, and various degrees of pain and shortcoming to their use. It should be a first class feature, and as simple to use as is in Go (including for cross compiling).

In general, if something exists in "some form" for 30+ years in a language, and only a handful of people use it, whereas in another it's used all the time and people from other languages are jealous of how well it works, then the formers "some form" is not a good one.


Regardless, it exists.


There is jpackage and jlink, which don't do single files but make single directory apps.

These days there's also GraalVM native image which does produce Go-like results. But with everyone using Docker on the server anyway it doesn't matter anymore. People who talk about single binaries are confusing to me. What are you doing where shipping one file is so much simpler than shipping a container?


> What are you doing where shipping one file is so much simpler than shipping a container?

Desktop applications.

Java would be a more popular desktop application platform if it weren't for the difficulty in this area (which, to be fair, isn't the only difficulty - cross platform is difficult inherently).


it's pretty hard for average Joe to install a docker engine before he can pull and run dockers, plus docker is not that great for cross platform desktop GUIs.


> everyone using Docker

Not really.


That wasn't a flaw. It was a "feature". Can yo you think of a way to increase market awareness of Java other than putting an icon on every computer in the world saying "Your Java needs to be updated" every two weeks?


jpackage and jlink are shipped in the jdk.


Fully compiled languages have another huge advantage. If you write a tool in Python and targeted say Python 3.10 then people using Phython 3.9 might not be able to use it. So, you really can't use latest and greatest features of Python 3.11 or 3.12. However, with Go, you can build your tool in Go 1.21 or whatever, and the user does not even need a Go toolchain on their machine.

I was planning to write a small side-project to generate GitHub Actions boilerplate. And this time, I intentionally chose Go for that exact reason. https://github.com/ashishb/gabo


Unless dynamic linking is used, or OS APIs change between version, or specific OS files change location.


Go binaries are traditionally statically compiled.


Hence my 2nd and 3rd points.

Also DNS uses dynamic linking unless configured otherwise, while being OS specific, then that are the libraries that rely on cgo.


Yeah, you can list 100 such cases.

Let me tell you the most common scenario. I have a fairly popular FOSS CLI tool written in Python. I cannot use the features from the latest version of Python or else I will alienate ~50% of the userbase. This problem does not exist with Rust or Go.

This problem would have existed for Java on Android except Google took the burden of "desugaring" the Java 17 code -> Java 8 compatible bytecode for the old devices.


Google dug their own grave here, Java is as backwards and forward compatible as it gets.


You cannot run Java 17 code on a Java 8 runtime without "desugaring."


I never see Dart talked about in these contexts but just to highlight a few things.

1. Compiles to native code with a single and very reasonable sized binary on pretty much any platform.

2. Compile to WASM (coming this year) if that’s your thing.

3. Excellent concurrency support with lightweight and simple mental models

4. Variables are not nullable by default thus simplifying tedious checking in your codebase.

5. Syntactically it’s the best parts of Java and JavaScript combined without all the foot guns and verbosity.

6. Full support for both OOP and functional code styles

7. Existing interop support with C, C++, Rust, Java and JavaScript and in the future WASI.

8. Fully static / compile time metaprogramming capabilities coming this year.

9. Also have maybe one of the best teams working on it that I’ve ever seen in an open source project anywhere. They put in a stupid amount of detail and care to try and keep everything pointing in the right direction at a macro level and have really strong levels of transparency around how the language is developed https://github.com/dart-lang/language

Honestly I think it’s critically under-rated and under-used. Most of its common criticisms I see about it are many years out of date.


Combining the best parts of Java and JavaScript isn't exactly something to boast about. Dart lives and dies with the Flutter framework. Other than that it's not doing anything special. Not terrible but also not a significant improvement.


I mean you’ve taken my point, cut it in half to remove the relevant context and are now arguing against a point you’ve made up as some kind of gotcha… I don’t know what you want me to say to that.

Same with your second point. I made a big point to explicitly say I think that right now Dart is very underused and has a huge potential outside of Flutter. Quoting that back to me as though it was something I hadn’t considered is equally as confusing.


I'm with you, I think that Dart is very sensible and very underappreciated language. But he does have a point in saying that currently Dart is tied to Flutter. Google is presenting Dart as a language that you write Flutter with, not as a separate entity. I'd much prefer if Google would present Flutter as a GUI framework for Dart, but alas. Perception is everything. As much as I'd love to start my next project in Dart I probably won't because I can't be sure that Dart won't end up in Google's graveyard in next couple of years whereas I'm pretty sure that Go won't.


I support that. I tried Flutter a few years ago, and I liked Dart and the Flutter way of defining the UI (compared to the old xml stuff in Android).

Then I moved to Kotlin + Jetpack Compose, and I don't see the point of Dart anymore.


You say Dart is underused, but why build something new with Dart when you could do the same thing with a better language? The only reason to use it is Flutter.


Familiarity comes to mind. Dart has developed into a nice direction with sound null-safety, extension functions, sealed classes and compilation to native&wasm. (The libaries weren't very mature when I used Dart, not sure if that changed.) I'd pick Kotlin because I'm familiar with it, but if mdhb is familar with Dart, maybe from his work with flutter, maybe from a time where you'd mostly use Dart to compile it to js, then go for it. Dart is a nice enough language to earn my thumbs up. (not that that would be important to anyone.)


Performance matters. Dart isn't even in the same league.

And its null handling and checking is the worst.

I absolutely hate it. Java over Dart any day.


Dart has excellent null handling[0] so I am not sure if you're perhaps talking of another language? It has had this since release 3.0, if I recall, and has been out for quite a while.

https://dart.dev/null-safety


What specifically about the null checking are you talking about?

I’ve never heard this criticism before and have no idea what you’re even referring to here but I am genuinely curious..


you still need wrap in Dart's runtime though, just like Rust, it's possible to pull in the libraries, it's just not static-link "friendly".


Since Java 9, developers can use jlink to provide a pre-packaged runtime environment bundled with the application. It's not static-link friendly or a single executable, but it does not require the user to pre-install anything or pull dependencies at runtime.


You mentioned "distribute software" but did not consider Electron for Node.

You can make an Electron app with JavaScript and ship the binary (or installer) on any platform. It's not a single executable file, but the user experience is the same.

I don't think there's an equivalent in Go that allows you build a desktop app like that (with frontend and backend both written in Go)?


You can do that with Java too. The JDK has jpackage, but if you want something better than I'm willing to sell that to you (or give it for free, if you're open source):

https://conveyor.hydraulic.dev/

It can be used to ship servers, we use it that way for our own servers. It sets up systemd so you don't need to use Docker. But where it shines is desktop apps.

Windows users get a little 400kb EXE that installs/updates your app and then immediately starts it without user interaction required, so it's effectively a single EXE. Mac users get a bundle. Linux, well, Linux users get a deb that sets up the app+its apt repo, or a tarball. Maybe in future a FlatPak or Snap or something else.

It knows how to bundle and minimize a JDK along with your app as part of the release process. Works great for desktop apps.

It's for Electron too, same deal, easier than Forge in my humble and very biased opinion.


Java GUI is less appealing though


That was true in 2005. Modern Java UI can look pretty great. Two examples:

1. Start IntelliJ and check it out. It's Swing but it feels modern and fresh.

2. Go to https://www.jfx-central.com/ ... JavaFX was introduced years ago to add far better support for modern visuals. JFX Central is a website written with JavaFX itself - it runs server side and streams drawing instructions to the client (implemented using a mix of divs, svg tags and css). You can do this because JavaFX implements the CSS2 drawing model. The website looks modern, like any slick website would today, but you can also download and run it locally. It uses Conveyor to do that in fact. The desktop version looks exactly the same as the web version.


how is jfx-central different from https://openjfx.io?


Different websites run by different people, I think. The former is more of a news site.

But yeah, arguably they should be unified.


but it would still be bettter and more performant than electron based apps.

a little memory heavy, but no more than electron apps while being less janky


For me I use neither, I actually picked wxWidgets after all those desktop GUI troubles over the years, not ideal, but it's the best I found for myself.


> You mentioned "distribute software" but did not consider Electron for Node.

Sorry for the snark, but users will undoubtedly be very grateful to them for not considering Electron.


I'm not so sure if the users of VS Code, Discord, Slack, etc would agree.

Yes it's bulky and sometimes slow, but it offers a lot of features and allow developers to ship features and updates really fast, while only needing one codebase for all platform.


I'm a user of VS Code and Slack and I'd be very happy if they didn't use Electron.

>Yes it's bulky and sometimes slow, but it offers a lot of features and allow developers to ship features and updates really fast, while only needing one codebase for all platform.

Like Slack having 1000x the memory and CPU use for 1/10th of the features a 200K app like ICQ used to have 25 years ago?


I feel vscode is quite good at hiding the fact it's not native, and felt quite snappy when I used it. Slack on the other hand feels painful slow and heavy (and very sensitive to network events like change of network or short disconnections, while native apps are generally much better add handling such glitches for some reasons)


I can't see users caring about it all being one single codebase and I don't think that they should. Every cross-platform development tool like this means awkward, non-native UIs. Maybe there's something to new feature development. But, I suspect the big players could be a little less profitable and deliver a better experience for their end users.

I've used Electron as a developer, too. While it may have been fast & easy at one time, I think that argument is losing merit. Electron had a lot of security holes and patching them has made common tasks harder. Security with Electron is a problem in general given its massive surface area.

Users definitely notice the performance issues. It's certainly not just devs complaining about abstract issues. Yes, people use these tools, but that's mostly out of necessity, not preference. Moreover, big players like VS Code have had to write swathes of the app in C++ for performance reasons.

I look forward to a return to optimizing for the user, not developer velocity. There will likely always be attempts at cross-platform toolkits and there may be a good solution in that space. I hope Electron isn't the best we can do.


> Security with Electron is a problem in general given its massive surface area.

I'm new to Electron development. I've read the docs recommending hardened runtime. Would it be sufficient for security? Can you give examples of such security issues?


Here I was referring specifically to pulling in a web browser as a dependency. Chrome pumps out security releases regularly for all sorts of issues. If you're essentially making Chromium the core of your application, you inherit those security issues (setting aside the fact it may happen in code paths you don't use). And, consequently, you need to keep up on updating Electron and distributing new builds to your end users, even if you haven't made any code changes.

I'm not suggesting every Electron app is a giant bag of vulnerabilities, just that you have a lot more to keep on than you would writing with GTK or UIKit. And, since everything is bundled with the application, you can't rely on OS updates to fix things for you. You need to cut a new release and distribute it.

If you follow the Electron recommendations on security you'll be on the right path. You'll just find common tasks have become harder than they were back when Electron was attracting people with its ease-of-use. I found trying to do type-safe IPC to be an exercise in frustration. If you read the old docs, tutorials, or books, you'll find IPC used to be considerably more free-wheeling. Locking it down is the right trade-off, I think. But there's been an accumulation of many small changes like that. As a result, I don't think the framework is nearly as easy to work with as when Slack or VS Code adopted it.


Thank you for your response. I have previously dealt with IPC for a different project and it was painful indeed.

But I found out that there is this new Preload module in electron that lets you use Node.js very easily via normal export and import. I'm using a popular starter template and it works great so far.


Haven't used VScode, but Slack absolutely sucks IMO (on Linux). I don't even want to install Discord, I use it from the website (but I'm pretty sure the Electron app would be similar to the Slack one).

> Yes it's bulky and sometimes slow, but it offers a lot of features and allow developers to ship features and updates really fast, while only needing one codebase for all platform.

So you are saying that it is bad for the user (bulky and slow), but good for the developer productivity. I really don't see how the users could like the fact that the developers are being more productive while making a worse app.


Using Discord for voice comms in CSGO on Ryzen 3600x w/ 32GB RAM had noticeable impact on my framerate, big enough that I refused to use for this purpose. No impact using Teamspeak. Both in Windows.


electronjs is probably the best cross platform desktops GUI,just a bit heavy.I actually use wxwidgets for GUI desktop apps.


The best cross platform desktop GUI is Qt.


whose license model is a mess unless your GUI is totally open source.


Isn't it just LGPL? Why is that a "mess"?


I'm not a lawyer, I wish it's as simple as "it's just LGPL", it's not.

Otherwise Qt has already conquered the desktop GUI world, it did not for a reason: the license mess.


It did conquer (-ish) the desktop GUI world, for a while: quite a lot of software was written in it.

And then Electron happened.


> And then Electron happened.

Which is worse for users, but better for developer productivity (probably nicer for developers too: C++ is not exactly fun).

I am still hoping that JVM Desktop apps come back at some point, maybe with Kotlin Multiplatform?


You can easily pack every single class file and resource into one single .jar if you desire to do so, no external dependency apart from JRE itself. Oh and of course no installation necessary, just put it anywhere with read access, all works.

At least in the past you could also make a single .exe out of it via 3rd party if you wanted, but I didnt use that for 20+ years so maybe its not valid anymore.


You just add the -static argument, if you want a fully static executable that can run on any linux distro: ‘g++ -o main main.cpp -static’

You can even go above and beyond with cosmopolitan libc v2, which makes c/c++ build-once run-anywhere: https://github.com/jart/cosmopolitan/releases/tag/2.0

There seems to be some work getting cosmopolitan libc support in Go, but it is not ready like it is for c/c++: https://github.com/golang/go/issues/51900

Edit: There can be some problems with using static, if you are using your system package manager for library dependencies. I would recommend compiling dependencies from source using CMake and FetchContent, this has solved pretty much all problems I have had with c++ dependency management.


If you want to distribute app to non tech people you do it via browser ;)


there are many apps that are not suited for a browser :(, on some embedded devices with a touch screen, it does not even have a browser and no internet connection either.


From my POV that is bad example, for those embedded apps you have technical personnel installing and configuring those. Non-technical users don't have any means to install/modify whatever is there.


> Go's major selling point to me is that you can ship one binary, nothing beats that.

Are .war files no longer a thing? ;)


Shipping one binary to install on a users machine has been solved by jlink and jpackage for ages.


How do you distribute a Go library? How do you distribute a Go library that has dependencies?


I'm not sure if your question is rhetorical, but Go doesn't have the concept of compiled library that you can link to another binary.

So in Go you make available the library's source code, and the end-user will download it at build time, or vendor it in the source tree.


Which is a non starter for businesses that don't ship source code.


Businesses that want to sell libraries without a source code license can ship APIs.


Not sure I understand what you mean here. How do I ship the API of my library without shipping the library?


Sure, if they don't care about performance.



Not only it doesn't work in all platforms, it uses a bare bones C API with unsafe code, and it hasn't been removed yet due to backwards compatibility, as Russ Cox already expressed his opinion that plugin was a mistake from his point of view.


> Plugins are currently supported only on Linux, FreeBSD, and macOS, making them unsuitable for applications intended to be portable.

uh-huh

but, in all seriousness: I wonder why that is? I struggle to think of a technical reason that go would be unable to load and invoke a .dll even if one had to name it .so https://github.com/golang/go/blob/go1.21.1/src/plugin/plugin...


Right, so it feels like Go hasn't solved this problem. It just completely ignores it.


I suggest reading about GraalVM...


> Go is simple. It's easy to understand, read, and maintain.

Matter of taste I guess, to me Go code looks ugly, it's too verbose with all that error handling every other line which hurts readability. Also the docs are often so cryptic and unhelpful, one needs to rely on examples elsewhere. I do use it though, when I need something fast in a single binary.


My main pet peeve with Golang is to use single letter or very short abbreviations when naming variables. The Golang documentation recommends what is considered a bad practice by most programmers communities.


Yes, short abbreviations are brutal in long sprawling code contexts. Write in a compact, functional, and readily unit testable coding style, and those tiny variables reduce your cognitive load and bring great clarity. Use comments and/or docstrings to provide context for compact parameters and local variables once, rather than implicitly documenting with every usage.


>Use comments and/or docstrings to provide context for compact parameters and local variables once, rather than implicitly documenting with every usage.

Context that isn't documented with every usage is context you need to remember. Trade off, but I'd much rather not have to remember multiple contexts for the same name every few hundred lines.


Are you specifically referring to method function receiver type annotations? Those are often left shorthand but shouldn't exist out of the type definition file anyway eh?


I’m referring to this guideline:

> Variable names in Go should be short rather than long. This is especially true for local variables with limited scope. Prefer c to lineCount. Prefer i to sliceIndex.

https://github.com/golang/go/wiki/CodeReviewComments#variabl...


One of those is not like the other...

lineCount is literally perfect meanwhile sliceIndex could be anything.


Exception handling and generics are missing key pieces from go. But adding them will make go look like java.

I just wish java would add null safety in the type system in a first citizen way.

For enterprise use java has no competitors. You have c# which is microsoft trying to estabilish nash equilibrium fu*ing the developers. I am a bit worried about ever increasing complexity and a steep learning curve, but seems like this is a problem on all fronts.


Java is an inferior language to C#. To get a superior language you would likely have to go for Kotlin. There is no comparison, because Java gets features today that C# had for years. Not even mentioning having to retrofit green threads because adopting async/await (used by TS, Rust, Swift and other languages) is impossible at this point.


C# does not have virtual threads. What C# has is async/await. Which was nice for its time, but at the same time it's an error prone design, as computations should never start async by accident, blocking should be the default. C# also has no useful interruption model. Java's interruption model is error prone, but at least you can work with it.

Async/await also splits the standard library and the ecosystem in 2 (blocking vs non-blocking, or blue vs red), and it can't automatically update the behavior of old code.

The introduction of virtual threads in Java also works well with "structured concurrency", as seen with Kotlin's Coroutines. Kotlin's approach to concurrency is also superior to that of C#, actually. But what's interesting about Java is that its evolution is often one that involves runtime changes, lifting all boats. Java engineers preferred pushing more changes in the runtime, and somewhat ironically, the JVM ended up being the true multi-language runtime.

Java is a good case study of how languages should evolve. It has extreme backwards compatibility, and features being pushed are assessed for how they impact the whole ecosystem, including libraries or languages not in Oracle's control. Project Loom was developed in the open, compared to what Microsoft usually does.


Do give async/await in C# a try, it has all the structured concurrency features other languages have to invent APIs and special syntax for :)

(if you want to take a look at good structured concurrency, you might be interested in Swift implementation)


I worked with C# professionally.

The async/await syntax works with the language's other statements, but for a long time it had gotchas. It doesn't qualify as "structured concurrency", and it has the aforementioned issues — it's (accidentally) error-prone, it splits the ecosystem in two, and has no interruption model.

I am not familiar with Swift, but I think you can hardly beat Kotlin's implementation. This is a good introduction from Kotlin's lead:

https://www.youtube.com/watch?v=Mj5P47F6nJg


What do you mean by "it splits the ecosystem in two"? I never observed such split, certain methods intentionally offer sync and async variants.

Interruption is achieved through cancellation tokens and has to be handled by consuming methods. There is no way around it because interrupting execution at an arbitrary point would lead to all kinds of issues regardless of the language (unless it implements some form of transaction abstraction and rollbacks all uncommitted changes).


Those "cancellation tokens" from C# are a band-aid.

In Java, you don't need to initialize those "tokens" manually, because the interruption signal is baked into Threads. Moreover, a lot of the standard library cooperates with Java's interruption, which is why you see plenty of methods throwing `InterruptedException`; and it's also reflected in types such as `CancellableFuture` or the `Flow.Subscription` (reactive streams). Of course, user-level code that isn't well-behaved, can end up catching InterruptedException, or resetting the interruption signal, without actually interrupting. This makes Java's interruption model somewhat error-prone, but it's workable, and at least it's baked in.

Note that interruptions could also be preemptive, as you don't necessarily need cooperation. If you think of the call-stack, or flatMap/SelectMany in reactive APIs, the compiled code could check the interruption flag automatically and interrupt the call chain.

And resource leaks aren't necessarily a problem, if the interruption protocol is well-thought-out. In Java, try/finally still works in the presence of interruption, since at worst you get an `InterruptedException`. It's not ideal because you can interrupt the interruption process, and in truth the ideal would be for interruption to be its own communication channel, complementing that of exceptions. But it's totally doable, and here I am familiar with several libraries from Scala's ecosystem, namely Cats-Effect, Monix, and ZIO that show it (with limitations imposed by the runtime).

Either way, what C# provides is basically next to nothing. In fairness, some C# libraries tried fixing it, such as Rx.NET, but it's not enough. And the aggregate result in the .NET ecosystem is that interruption is not something people design for. Like what to interrupt a network socket? This ends up being a setting, presented as a timeout in case of inactivity, as a configuration of the connection, instead of a higher-level generic function that can be applied on the consumer side. And the probability for resource leaks goes up actually, because in the presence of concurrent races, you really need interruption.



Except when there is only one version and someone has to write the transition code.


> regardless of the language

That’s a choice, but it is unfortunate for the programmer. The truly right way to do it all is like Erlang does - where all processes are cancellable and nothing bad happens.


There shouldn't be a reason for a managed language to introduce function coloring. It's horrible.


But async/await is special syntax. I agree it's a nice improvement over using Task APIs directly.


And don't forget to bookmark David Fowler guidelines to avoid all the gotchas that everyone falls into while using async/await.



Java, prints 1:

    Set<File> s = new HashSet<File>();

    s.add( new File( "c:/temp" ) );
    s.add( new File( "c:/temp" ) );

    System.out.println( s.size() );
C#, prints 2:

    ISet<FileInfo> s = new HashSet<FileInfo>();

    s.Add( new FileInfo( "c:/temp" ) );
    s.Add( new FileInfo( "c:/temp" ) );

    Console.WriteLine( s.Count );
C#, test fails:

    string path = "/tmp/filename.txt";
    FileInfo f = new FileInfo( path );

    File.CreateText( path ).Dispose();
    Assert.True( f.Exists );

    f.Delete();
    Assert.False( f.Exists );
https://en.wikipedia.org/wiki/Principle_of_least_astonishmen...

Languages have their strengths and weaknesses.


FilInfo doesn’t override Equal operator in C#. HashSet will use it for comparison and those are two different object references so idk what the confusion is here.


A set is a collection of objects in which order has no significance, and multiplicity is generally also ignored. Observing the code alone, it isn't obvious that two FileInfo instances having the same path are themselves not equal. As has been mentioned elsewhere, FileInfo should not be hashable to prevent declaring it as a Set, avoiding the situation entirely.

C# isn't flawless. Java isn't flawless.


You can’t decide language superiority by the number of features. While C# indeed has many cool features, I do think it has already went into C++ territory where all the different, independently cool features have very non-trivial interactions that make it very hard to reason about.

Also, async-await is not a good thing — virtual threads are superior in every way in case of a managed language.


Why async/await is not a good thing? What makes green threads better? Do you know the difference, the issues async/await addresses that green threads simply do not?


This is code with async/await:

  async fn read_file(filename):
    f = await os.open(filename)
    let data = f.read()
    await f.close()
    return data
this is equivalent code with green threads:

  fn read_file(filename):
    f = os.open(filename)
    let data = f.read()
    f.close()
    return data
As you can see, async/await is pure syntactic noise, it doesn't convey any important meaning.


Async enables clear contract for a type that represents a delayed result.

Better implementations offer eager execution and allow to easily interleave multiple concurrent futures/tasks, like C#. Green threads on the other hand are a workaround to deal with blocking for the most trivial case of cooperative multi-tasking, offering little beyond that.


> Async enables clear contract for a type that represents a delayed result.

It's not really a type, otherwise it would be something like Future<T> in Java and plenty other languages. It is usually implemented as a transformation to a state machine.

Also, Loom is M:N and calling them green threads doesn't give you the whole picture at all. Not exactly sure what you mean by easily interleave -- functionality wise the two is more or less equivalent. You just get to keep your simpler mental model (and tooling) with virtual threads.


with green threads, every result is "delayed" and available exactly when you want it (i.e. as indicated by naive reading of the control flow in code)

> eager execution, interleave multiple concurrent tasks

green threads give you that for free

> deal with blocking

blocking is an implementation detail; both async/await and green threads can be implemented on top of either traditional blocking IO, or callback/non-blocking IO

I'd be even happier to discuss concrete code blocks / examples, that's where the superiority of green threads truly shines


Because they were created to manually run multiple threads on one physical thread, which is the kind of decoupling that a VM/OS is supposed to do for you.



Read David Fowler's guidelines.


Do you have link to any specific article of his (on virtual threads vs async)?



C# is turning into C++, sadly.

It appears every six months there must be new language features being added.

The last one, for declaring fixed size arrays in structs, with an annotation instead of proper grammar change is getting ridiculous.


F# is far superior to C# as a language. If you are going to jump to the .NET runtime that's the tool to go for.


apples and oranges


C# is a better language, but the JVM is a significantly better ecosystem. Java will always be behind (and type erasure for generics is annoying enough as someone who started in C# that I don't think it will ever catch up) but between hotspot and being the preferred language of 2/3rds of the clouds I think it clearly wins.


Type erasure is why there's such a rich ecosystem of 3rd party languages that run on the jvm.


Meaning that it would be harder for those languages on the clr because of generics? Would typing all generics as Object not be essentially equivalent?


   type erasure for generics is annoying
I too have spent significant time programming in both C# and Java. This complaint about type erasure in Java: When does it affect your daily life? There are so many craft workarounds available now that it hardly comes up anymore.

Also, would your opinion of Java significantly change if type erasure was removed or never existed?


I have much more experience in C# then Java FWIW, so you're welcome to take my opinions with a grain of salt. I've had a couple of really annoying errors with Beam serializers that took me way longer to debug then I thought was worthwhile.

I think it is a worse choice, it's too ingrained in the language and ecosystem to change. That and a couple of other nits would likely make me pick Java over C# or Go if I was starting a new project going forward.


If you like jvm, use closure instead.


Sorry you’re down voted. Closure might not be what the author is looking for.

There are a lot of languages you can use with the JVM, Closure is one of them, Groovy, Scala and Kotlin are some more. Kotlin is gaining a lot of traction, especially since it’s backed by JetBrains.


Clojure*


Java 21 doesn't retrofit green threads though. Quasar [0] is a library that implemented fibers for Java and the main developer pron has joined the OpenJDK development team. All that was necessary for first party support is to make the JDK libraries yield when blocking.

Adopting async isn't impossible at all, there is very little demand for it.

[0] https://docs.paralleluniverse.co/quasar/


Async/await is an inferior product compared to threads, for so many reasons (function coloring and meaningful stack traces come to my mind). Keeping the same interface as native thread, plus structured concurrency, are best of breed.

Many uncancellable threads + mutability + goroutine panic kill it all are serious issues for golang.


> I just wish java would add null safety in the type system in a first citizen way

With valhalla (value types), it might just come!


Afaik, no language as great of exceptional handling as Java. I prefer Go but I have to admit, Java's checked exception handling is amazing.


That is an extremely debatable topic. Which is why kotlin completely ignores checked exceptions.

Frankly, Id rather have a result and optional/nullable type like in rust/kotlin than deal with exceptions in any capacity.


Kotlin’s decision to make every exception runtime is the main reason I don’t use Kotlin. It’s especially baffling that they realized the issue with implicit nullability and got rid of it (though not with Java methods, which opens another can of worms), then went and intoduced implicit “exceptionality”.

The correct way to deal with Java’s checked exceptions would have been introducing a Result type, or, preferably, type algebra, like in TypeScript, so something like:

  fun openFile(fileName: String): File | FileNotFoundException {…}
Then you could handle it similarly to null:

  val content: String | FileNotFoundException = openFile(“myfile.txt”).?read()
  …then you have to do a check before you use content as a String…
or

  val content: String = openFile(“myfile.txt”)?.read() ?: “File not found”
(There could also be other possible ways of handling the exception, like return on exception, jump to a handling block, and so on.)

In fact, null should be treated as an exceptional value the same way all exceptions should be.


This would be my preference. I like `Either`or some other container type, but a simple union that makes exceptions explicit works also.


Checked exceptions are gross, they get in the way when you're prototyping, and you end up just ignoring them anyway (since you're prototyping)


The ergonomics of checked exceptions may be debatable but compared to golangs explicit error handling at essentially each function call is definitely worse.


Yeah, I've got a lot of Java experience and a wee bit of Go language experience and I agree with you. I like Go in almost every way except for it's error handling. It's just wrong to have to check every goddam function one by one


I guess that's the reason why most Java programs I use cannot do proper user-facing error messages. Because it is so easy to just ignore error handling. The exception will be caught by the top-level, right? This is how almost every Java cli tool prints a stacktrace on even most trivial user errors like file not found.

Having to deal with errors and forcing the developer to do proper error handling is a good thing.


I don't mind Go's errors. I do mind the complete lack of hierarchy in that. Java's exceptions are hierarchical. I can create an exception that specializes from `IOException` and that I feel is really powerful. Go added this half-baked and much later. So, most FOSS libraries don't support it yet.


Both Java checked and unchecked exceptions are inferior to signaling errors by return values like Go/Rust/Haskell do.

Exceptions are not composable, cannot be generic, and it is not visible in the source code which lines can throw, so every line is a potential branching point.


I don't think the jury will ever return on this topic.


Go has had generics for more than a year now


I agree, Go has had generics for a little while (of course they are different from Java's generics because Go's type system is different from Java's type system) and I don't think the parent comment writer was aware of that.


Not even in the same league as Java.


What is “enterprise” use? Plenty of enterprises use Go.


By enterprise I mean a lot of features related to data consistency, scalability and integrations.

I do not know about go ecosystem, but java and spring have mature solutions that cover the most advanced use cases.


Kubernetes is what I would call very advanced use case.

But that's the thing with Java, yes there are libraries for everything and one could see that as a problem actually.

Spring was mentioned before, but it's the perfect example of an overengineered/ heavy library that does a lot of black magic.


Java is strange because it can be both verbose and magical at the same time.

Just the other day I ran across an issue where spring was wiring things up correctly on Linux but not Windows.


I too dislike dependency injection frameworks. I try to always wire manually myself. I'm tired of debugging old SpringFramework apps where changing the JVM by a patch level or changing one JAR dependency breaks the wiring. I have seen it too many times to count.


Originally prototyped in Java, rewritten in Go when a couple of advocates joined the team.


If all you have is spring boot, everything looks like a spring boot problem.


Plus Spring is honestly quite badly programmed.

The pagination object is bulky and unnecessarily complex, where a simple offset/limit is enough (and a nextUrl for cursor-based access).

When we looked into cluster locks, they’re not even released if one node goes down. I mean, who would need a lock implementation that just stores a line in a DB? And the doc doesn’t even warn about it.

Apache contributors were much better-skilled.


    Apache contributors were much better-skilled.
That is quite a broad statement. Two negative points about Apache Java libraries I can think of: The original "lang" libraries have not aged well at all. Also: The HTTP client libs are a fiasco. Very challenging APIs and weak documentation.


Not really. Software first enterprises do. But most enterprises that have a non-software focus generally prefer languages that have been around for a while and are known by lots of people.


I’ve written go code for large corporates in Australia who absolutely did not have a tech or software focus. One was a notoriously bureaucratic company which used to be a government owned enterprise. For the niches it fills well, it’s become almost ubiquitous.

With regard to this:

> But most enterprises that have a non-software focus generally prefer languages that have been around for a while and are known by lots of people.

I know developers who work for large blue chip financial sector and other traditional sectors. They’re on the same React/Vue/Webpack/whatever treadmill as all the frontend devs devs working at web dev agencies. And they’re often compiling it down from TypeScript, which has not been around for a very long while at all and is not known by lots of people (relative to the size of the JS community.

Swinging a bit of Go for a service in an environment like that isn’t really that hard.


By enterprise he means non-webshit industry.


Wow


The programmers don't make that call. The CTO does, so they can hire anyone.


This is definitely one of those. “Tell me you don’t know .NET with out telling me you don’t know .NET” moments.


> Exception handling and generics are missing key pieces from go. But adding them will make go look like java.

You know that Go has generics since a couple of years now?


Coming from Spring Boot with it's 17+ level deep abstractions to Go / gin-gonic was such a breath of fresh air!


With the little difference that spring boot offers you 99% of your needs, both to fetch the data from (sql, nosql, you-name-it), and to offer your interface out (web, rest), and programming style (syncronous, asyncronous), and observability, ....

while for go you'll find yourself deep in the mud of choosing what 3rd part library to use for logging and how to make it work with the rest of custom stuff you have to write


it's kinda funny to me when people with latest macbooks, loaded with actually useful but comically expensive programs, open up goland that itself has a shit ton of features, to write some error handling in go


I went the other way as I was sick of all the repetition in Go. Spring boot/JPA data repositories save so much SQL, and spring boot automatically marshals to and from JSON, form data, etc. It means you can just work on the meaningful business logic. Add on Lombok and there's even less to maintain.


Go is Java 1.0, nowadays 1.5, as they thankfully finally at least added some support for generics.

I am not touching Go, other than on the projects I have some customer or higher up telling me to do so.


Go gives you the illusion of simplicity because it gets rid of guard rails and error checking. If you remove all error checking, of course your code is going to look a lot simpler. It's also going to be a lot more wrong and crashy.

Wait until your code base grows, your team grows to >10 developers, and you will understand what I mean.

Java (and preferably Kotlin) are a lot more serious about making sure your code is robust before it compiles.


Wait, are you implying Go doesn't have error checking? In what way? It has famously verbose error semantics.


It's verbose yes, but also more error prone.


No exhaustive enum checks.

No Option type, so you can still use results of a function if it returns an error.



I've heard this argument before, and I point out Kubernetes to them. Is that code base complex enough, because it's pure Go doing just fine and runs on plenty of systems?


You can point to a complex project in any language, and that would prove nothing.


I mean, that’s pretty much go’s first complex application that was specifically written with that, so don’t think it is a good example.


> Java (and preferably Kotlin) are a lot more serious about making sure your code is robust before it compiles.

You are making great points for Rust, Ocaml and Haskell.


There isn't all that many plus in their type systems that is not expressible in Java. OCaml and Haskell has Monads, sure. There is Scala for that on the JVM.


> There isn't all that many plus in their type systems that is not expressible in Java.

Thanks to Turing, anything that is Turing-complete can duplicate any other thing that is Turing-complete. So, yes, you can do all of Haskell's types in Java. That is not a flex.

The flex is doing them in a non-horrific way.


The kind of Turing completeness certain type systems have is useless beside academic curiosity - look at the vavr library, that’s what I’m talking about. You can have plenty Haskell types without any hacks, except for Monads in Java.


> vavr - turns java™ upside down

"turn X upside down" are different words for "use X contrary to its original intention and design".

You will find very few people willing to let go what people undestand as Java so as to be able to do Haskell-in-Java.


It’s goddamn immutable collections and Optionals, not brain surgery come on.

It’s not like Java hasn’t been going in the same direction, see records, sum types, pattern matching.


Yes, it's goddamn immutable collections and Optionals.

People still hate it because it's immutable, therefore you can't do hashmap.add(), and it's hard because they can't randomly return nulls, and it's slow because the hashmap now takes o(log n) always instead of o(1) sometimes and o(n) other times.

People hate it because it's different to the Java they learnt 20 years ago, that they claim is exactly the same as today.


Is it possible to express Rust enums (tagged unions) and especially the option type in Java? Ofcourse. But the power of good type system doesn't come from the fact if you can express something, but rather how seamlessly it is integrated in to the language.


Rust has sum types named wrongly as enums, which java also has as sealed interfaces. The option type is just one example for a sum type, which is as easy to express in Java as

  sealed interface Option<T> permits Some<T>, None<T> {
    record Some<T>(T value) {}
    record None<T>() {}
  }
Sure, you have written 3 words more than Rust, and?


Go has made many decisions that i'm not happy about.

If they did one thing exactly right on the language level, it's the [lack of] OOP: interfaces in their implementations, and no inheritance, overridden methods, covariance / contravariance games, etc.

You can of course write in Java in that style: only extend interfaces, never inherit classes. But many libraries, including the standard library, actively refuse to cooperate.


And yet even after writing Go for 5+ years I still have to Google for what actually implements Reader so I can read a file, etc.


Do you mean overloaded methods? You can "override" methods in Go in the sense that if you have an embedded struct you can provide your own implementation in your struct to use instead


Go also sucks. It's so easy to leak threads.


I never understood some of the objections to inheritance. It always made it easier for me to reason about the code, not harder.


Some people are aware about various concepts/paradigms and are able to put each to good use where appropriate.

Other follow fashion and if some dick looking for fame declares A as obsolete and B as the new and shiny one (to be replaced soon anyways) then the lemmings would follow and sing the gospel.


This is correct. It is about culture too. Java is the kind of language that attracts some mediocre programmers who produce mediocre code based on the wrong ideas of object oriented programming. of course, there are great Java programmers. It is just that a better programming language would help average programmers like us to achieve more


That escalated quickly. There are good and less good people everywhere and using language X does not automatically tell anything about you.

If anything I think the cases where you can use Go successfully are only o fraction of the cases where you can use Java successfully. It's not necessarily Java itself but the ubiquity of the JVM and its ecosystem (see Kotlin, see Scala as examplea that have leveraged this ecosystem successfully)


So the ideal blub language will be whatever language is too new to really be the legacy thing with tons of black magic you just have to know, but also too old to include tons of new ideas in language design the programmer has to grapple with.

Old languages force you to understand really core issues because the stack is 1m+ lines of code and you need an operating model for all that magic.

New languages do the same thing, but it's because half the really good stuff is <experimental>

Python and JS are in the current sweetspot, go is up next, and after that, Rust.


> the cases where you can use Go successfully are only o fraction of the cases where you can use Java successfully

could you elaborate why do you think so, and maybe give examples of cases where Go can't compete with Java?..


Libraries for application programming, last time I used Go nothing came close to the Java PDF creation libs for example.


Imho Go is really good for self contained micro-services. The way Go does binaries and the cross-compilation part are great.

Otoh, Java has a huge ecosystem. Huge. This plus dependency managemnt make it the first choice in most cases. Not even going to go into the massive innertia given that Java has been around for decades (who is going to rewrite everything in go?)

Today most of the time I pick Kotlin which is sort of whatbJava could have been (or maybe will become) with proper investment and care.


Let's not forget who golang was made for:

"The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt. - Rob Pike


You're entitled to your opinion. I guess I must be a mediocre programmer then. (Don't forget that 90% of developers thinks of themselves as above average.) But if Java can evolve, why wouldn't its practitioners?


In the past, when java was dominant and most popular language - sure.

Today though, with languages like js being the most popular one, I would be surprised if inflow of mediocre developers into Java world would be even the same, not to mention higher, then with those more popular languages (Go included)


This generalisation is easy to explain. On the server-side, Java "won" the enterprise battle. In the last 20 years, big corporates re-wrote their server-side C/C++ stack in pure Java (and thick desktop clients in C#). It is so much easier to maintain than C/C++. As a result, they can hire mediocre Java developers to maintain their "new" legacy services.

    who produce mediocre code based on the wrong ideas of object oriented programming
This overlooks this history of OOP. Each decade, lots of new ideas have emerged so that skilled programmers continue to use the same languages (Java, C#, C++, C), but change how they use them. C++ in 1996? Let's fight about diamond inheritance! C++ in 2006? C++ in 2016? C++ in 2026? Repeat for all four languages that I mentioned. The story will look similar. Anyone good writing new code in these four languages isn't using many levels of inheritance. It is gone. Sure, it exists in the language for historical reasons, but it is hardly used in new code. "Prefer composition over inheritance."


And some people for no fucking reason think that they're smarter than everyone else and are qualified to tell others how they should go about they business.


> attracts some mediocre programmers who produce mediocre code

That will be reality for most companies, no matter the language. And if Java projects still got delivered in such conditions - it might also be a mediocre language but it is also just good enough.


Yeah that's not going to be too popular but Java in particular has a tech culture problem. It's way more common to find overengineering and overabstractions in Java projects than any other language.

The language itself might be fine but I'll never touch it because of that.


Despite the fact that this is a claim with no basis in reality, what you are saying is that people who don't understand the core concepts the language is built on write bad code...Isn't that true for every language?


This is also the killer argument for going Python, by the way. Go is going to be much faster, of course, but you can still scale quite well with just going Python. And sometimes / most often you don't need scale.


I use Go and Java every day, and Java is way more enjoyable to use


Lol...you use the language most appropriate for the given situation. What's appropriate in the browser....hmmm. Can't slam your foot down on that one.


> The packaging is like how you would package files on your computer in single folders

How does that vary from Java?


Java 21 has me anticipating the next JRuby release because of Virtual Threads. Charles Nutter gave a talk about JRuby in August where he showed a demo of the impact on Ruby fibers and it’s pretty significant.

There’s a lot that I really like about the JVM and it’s tooling, I just don’t like writing Java code anymore. JRuby kinda gives the best of both worlds.

Here’s the talk. Virtual thread demo is around the 45 minute mark.

https://youtu.be/pzm6I4liJlg?si=GtxQ4MThEaNDfC67


I loved this. Is there any post comparing Ruby performance with JRuby?


There are lots. Usually these days, also comparing with TruffleRuby, which also runs on the JVM.

This one is a couple years old now but you get the idea:

https://eregon.me/blog/2022/01/06/benchmarking-cruby-mjit-yj...

I don't know where to find more up to date benchmarks.


How does jruby relate to truffleruby?


He actually talks about that during the video as well. Short answer is “it depends”.

IIRC Truffle is dramatically faster in some benchmarks but slower in others. And there are some usability aspects of the language that are negatively impacted.


I think he shows the performance of running JRuby on top of GraalVM. GraalVM uses Graal (the JIT compiler) in place of C2 (the JIT from HotSpot).

But TruffleRuby is something different. It is another Ruby implementation (just like JRuby is a Ruby implementation) using the Truffle framework. And Truffle framework requires to be run on GraalVM.


Wondering if anyone is using JRuby on Rails in production.


He listed off a bunch during the talk.


> If there was any feature that would sway existing Golang developers to switch to Java, it would be this.

Not sure about this, but a trajectory question: why does the Go community have so few concurrent containers but Java community does? I mean, even `sync.Map` is specialized for two specific use cases instead of like Java's ConcurrentMap that is of general purpose. And In Java there are concurrent sets, queues, barriers, phasers, fork-join pools, and etc. I'd assume that even with Go's go routines, we can find great use of such containers, right? At least fork-join is not that trivial to implement. And using mutex everywhere seems just so... low level.

I understand that there are 3rd-party implementations, but concurrency is so tricky to get right that I would be really hesitant to adopt a 3rd-party package, unless it is as mature as Java's JCTools or Google Guava, both of which are also backed by a large number of users and developers.


Because the predominant patterns are to do concurrency with message passing, so having multiple goroutines trying to mutate the same container is the exceptional case (though as you say, mutexes are the answer when needed). Another reason is containers are embedded features not stdlib, which has huge implications to how willing someone is to go against the grain, for better and worse.


Ironically, golang does not have constructs to build immutable types to pass them around, while Java does (records).


Yet another reason is that you really want generics for container types and Go's generics are relatively new


What concurrent data structures that Google Guava offer than the standard library does not?


`Executor.newVirtualThreadPerTaskExecutor` versus `go` really gets to the heart of why I think that Go developers aren't going to be switching.

edit: Sorry, it's actually:

    try (var executor = 
        Executors.newVirtualThreadPerTaskExecutor()) {
       executor.submit(...)
    )
instead of `go`


You’re likely to need a lot of boilerplate that “go” statement won’t generate: pass and wait for completed results, report errors, timeouts, cancellation, bounded parallelism, pushback, service monitoring.


My comment is perhaps too glib. I'm not trying to say that Go is better, I frankly like Java more. I only mean that if I'm in a Go mindset and I read the linked document on virtual threads, and I see that code example, I'm going to close the tab.


Starting a virtual thread in Java isn't that clumsy:

   Thread.ofVirtual().start(() -> System.out.println("Hello"))


The go example is missing the channels, select, wait group, context, cancellation

Doesn’t sound like a fair comparison thou the Java version is missing things


> The go example is missing the channels, select, wait group, context, cancellation

The Java version would require all of those in the exact same way though.


Java has structured concurrency in the works, which will not be much more verbose, unlike golang.


If you want to use that with more concise syntax, there is Kotlin for that. In which case abstracting that is one line of code tucked away in a utility file:

    fun <T> go(body: ExecutorService.() -> T): T =
        Executors
            .newVirtualThreadPerTaskExecutor()
            .use(body)
Now you can write:

    val result = go {
       val result1 = submit { slowOp() }
       val result2 = submit { blockingOp() }
       result1.get() + result2.get()
    }
or words to that effect. You can also reduce it a lot in Java too, as mentioned by another commenter.

    class Shortcuts {
        static <R> R go(Function<ExecutorService, R> task) throws RuntimeException {
            try (....) { return task.apply(service) }
        }
    }
and then you can write:

    var result = go(service -> {
        var result1 = service.submit(() -> slowOp());
        var result2 = service.submit(() -> blockingOp());
        result1.get() + result2.get();
    });
using static imports.

Now if you're making a cultural point then sure, Java APIs tend to be named quite explicitly. It has advantages when searching or auto-completing, and it's easy to wrap with aliases if you want to abstract a boilerplate pattern away.


Wrap it in class Go { static void go(…) } and it will look better with “import static”


I assume `Go` would have to be Closeable and you'd still need the try-with-resources, right?


You don't need a try-with-resources, you can manually call close if you want, or has to work in a non-lexical scope.

But you often want the concurrently running threads to return some results, that try-with-resources is part of the structured concurrency that helps you with closing off a branching point, similarly to how we use while loops instead of gotos. `go` in itself corresponds to `goto` basically, with many of the same negatives.


Maybe you re-read my comment, check the signature of Go::go() and think again?


So basically executor.submit(). And you still have a language that is significantly less verbose than go, objectively.


Yes, if you ignore more than half of the code it's quite concise.


How many thread pools do you want to use? You only need that once.


go will always lose vs java when it comes to code verbosity


Extend that to a full function and the Go code will be 2x more verbose than Java with all the `if err != nil` stuff.


Java 21 also previews structured concurrency (https://openjdk.org/jeps/453), which uses the virtual thread implementation. It seems really nice, at least from reading the examples, and removes a number of pain points when dealing with thread-based concurrency.


> The biggest feature in Java 21 is the release of Virtual Threads

Is there a good honest writeup on why is this interesting? Very curious.

The earliest JVMs had this, green threads. Performance was terrible so it was eventually dropped.

I want to fully utilize every CPU and every core I have, why do I want green/virtual threads again?


The JVM automagically swaps out blocking network calls executing in a virtual thread to a non-blocking implementation, so potentially many other requests can be served in the same time.

So you get the benefit of async frameworks, with none of the negatives: the blocking model is trivial to reason about, read, maintain, and works perfectly with tooling (you won’t get an exception in some WhateveScheduler, but at the exact line where it actually happened. Also, can step through with a debugger).


Thanks. Any good books that cover the current java performance landscape but is grounded in the history of what came before?

I used to be heavily involved in java-based server performance and scalability work back in the 90s and 00s but have been working on other things the last decade. But it would be fun to learn more about where things stand.


> I want to fully utilize every CPU and every core I have, why do I want green/virtual threads again?

If your task is CPU bound, then virtual threads don't gain you anything.

On the other hand, if your task is IO bound, e.g. for a webserver, virtual threads make it trivial to spin up a new thread per request, for massive workloads.

https://en.m.wikipedia.org/wiki/C10k_problem


It's not the same. The earliest JVMs weren't really thread safe at all but machines were single core so it didn't matter, you could just cooperatively multi-task.

Later HotSpot became thread safe (and fast - a rare achievement), so started using real OS threads so it could go multi-core.

Virtual threads are M:N threading. There are multiple native threads so you can exploit all the cores, and also user-space runtime managed stacks so you can pack way more threads into a process.


> The earliest JVMs weren't really thread safe at all but machines were single core so it didn't matter, you could just cooperatively multi-task.

In the 90s we didn't have multiple cores but we had multiple CPUs. I started using java in '96 on a 2 CPU SPARC and the lack of real thread support was limiting. When green threads was dropped in favor of real (OS) thread support there was much rejoicing. I worked primarily in server performance back in those days.

> Virtual threads are M:N threading.

Solaris had M:N threads early on but it also was dropped.


For me, it's the difference between code structure and resource usage.

Green threads are for a cleaner code structure. async/await comes quite close, but requires function coloring, creating high coupling. The threads have to have low overhead, so that I don't have to think about whether I should create a new one or not.

Kernel threads are for parallelism and scaling across cores.


From my vantage as a Clojurist, virtual threads on the JVM seem to have very minimal benefits. All I can think of at the moment, is that we could have thread-safe JDBC connectors: https://medium.com/oracledevs/introduction-to-oracle-jdbc-21...


I mean couldn’t you have the performance equivalent of core.async’s go except with the ability to take across cross function boundaries? That seems pretty huge.


I don't think Golang devs are waiting to switch to Java if only making a lot of threads was easier.

Honestly, its way too early. Virtual Threads will need a a "killer app" (in this case a killer framework) to pull in devs.


Like Helidon Nima, Micronaut, Quarkus or recently, Spring?


I haven't really used or heard of the others but Spring was already quite good and you really didn't need to think about threads very much. This might help some benchmarks but, again, who is pining for "Spring but with more threads?"

I'm thinking something that would be a clear differentiater such as a multithreaded GUI framework.


The great thing about virtual threads is they're basically a drop in replacement for threads.


For cases that are IO bound yes. Threads still have their place (another thing that golang doesn't have).


Go almost instant build time is a huge productivity boost when compared to Java. You get both the solidity of a typed language, and the development speed of interpreted languages (py, js).


Java's build times are very fast. Java's build tools are anything but. And I'm surprised no one is doing anything about it.


That's because everybody is busy shipping full web browsers with a hard-coded website inside and calling that an "app".


Gradle, when the daemon is runnning is very fast, people just often fck up their build scripts to include config-time run functionality, which is just all around a stupid thing to do.

It only ever runs tasks that actually have to be run, has integration with javac, can work in parallel, and even has cross-company build caches if needed.

Also don't forget that Java can do hot reloads with the debugger, or with tools like JRebel. Certain frameworks support it 100% and it will be much much faster than whatever go does.


I've never seen Gradle run fast, even with the daemon running. Gradle itself can take multiple seconds to startup. God knows what it's doing.


Gradle is steaming pile of garbage. But since the zoomers are allergic to XML, Maven (actually fast) is slowly dying.


We are in the process of switching to Gradle at work and I'm not sure I like it. I used Gradle when building some OSS projects, but my experience wasn't that great.


Just make sure that whoever writes the majority of the build file actually understands gradle, at least its fundamentals. It really is not hard, and afterwards it is a really great tool that can significantly improve compile/CI times.


The tool whose main purpose is to build stuff should not demand "understand gradle at least fundamentals so that afterwards you can probable possibly improve times".

Why isn't it fast out of the box? Why does the simple "provide a list of deps, provide a list of paths, build" take so darn long? Because Gradle is a great tool or something?

Don't forget that it's also already at version 8, where each change is mostly incompatible, bizarre and inexplicable breakages between versions, often due to meaningless option renames. Imagine if they spent all that effort on actually making it a great tool.


Gradle is much faster than maven as it can properly parallelize the workloads. Sure, it won’t be visible on a hello world, though.


I've never seen gradle be faster than Maven. I have no idea what purose its daemon serves or its promise to "parallelize workloads". Which workloads? Its purpose is to build the project, fast. It spends about a magnitude of time more just trying to start up, even with daemon running.


There seems to be some disagreement to your statement. Can you provide some concrete examples?



Has never been the case in my many years of using it. Hundreds of MB permanent RAM usage for the daemon (which it wouldn’t require if it was properly designed), everything taking well above 10s. None of this has ever been an issue with Maven.


You can disable the daemon in gradle if you don’t want it.


Gradle is anything but fast


If you are leveraging Maven, use mvnd during development: https://github.com/apache/maven-mvnd


If you avoid pre-Java 11 modules in Apache Maven, it is very fast to build. Plus, IntelliJ is auto-magically incremental build. Can you give some specific examples where Java build tools are slow? Most Java developers spend their whole day in an IDE that is doing incremental build, jumping in and out of a debugger to fix bugs or broken unit tests.


Maven is okay-ish, Gradle is an abomination. Incremental compilation doesn't always work, and the fact that the build tools often spends a magnitude or more time just starting up than actually doing useful work is inexcusable.


I once posted an Ask HN: what are your build times. A golang developer stated their 25 million line project compiles in less than half a second. If that is really the case, I must say, this is very impressive and potentially a reason to switch from other languages.


I work on a product written in Go which is considerably smaller than 25 million lines and our compile times are... really... really long. I really don't believe that claim at all. If it's true, I would love to see how it's done because that is SOOO far away from my daily experience.


I was skeptical as well. Thanks for providing feedback. I suspect the way they were counting lines was wrong as 25M lines is a very large project in its own right.


Isn't this an exaggeration ? I have a 100k line go project and it takes several seconds to compile. No idea how a 25m line project compiles in less than half a second.


Java compiles very fast especially with a running gradle daemon, definitely on comparable levels. It also supports hot reloads to a degree.

Also, was that a clean build?


I wonder what will happen with these patterns now. They seem to have been invented to work around the problems with regular threads. Will new patterns emerge, or will the reactive patterns be ported to run on virtual threads?


The reactive patterns are now obsolete and will die. And good riddens I say.


I wonder: are virtual thread at the JVM level? If so, would Kotlin (and other JVM languages) be able to leverage virtual threads to improve (e.g. produce more optimal JVM bytecode) for their async functionality?


It is a JVM level feature but the Scala guys at ZIO are not that excited about it IIRC.


That is true, but there are many who have discussed that. I wanted to bring some attention to the new syntactic changes Java brings that can help with implementing better logic.


It depends what one needs. VT are nice, but I don't use threads much, so it is a cool feature I won't use.

For me bigger feature is pattern matching for switch and records.


No thank you. There is limited time in the day and go fills my needs.

Go, Rust, Ruby. These three cover all cases.


Add Python to that list (for the ecosystem) and JS (for front end web dev)


Will that also make existing Sway developers go?


The title of the blog post is IMO a poor choice. The (hidden) subtitle of the post is "Algebraic data types in Java" which is much more descriptive of the content. A better title would have been "Algebraic data types in Java 21".

Perhaps because of the title, many/most of the comments here are off-topic. I was hoping to see more discussion about algebraic data types, strengths and weaknesses of the Java implementation, technical comparisons to other languages, etc.


Yes, I'm also not really sure I want to see algebraic types in Java even though I would prefer if a language that focused on algebraic types was more popular.

All the existing Java code doesn't go away, so is it really going to be nicer to have code like this mixed randomly into that?


Of all the features most Java devs (and ex-Java devs) desire, algebraic types are near the bottom.

How about intersection and union types? (NO, sealed classes are not a substitute for unions).

But, yeah, in my view JDK 21 is a a disappointment. I rarely want pattern matching, but I would like properties please and records are nice, but actual tuples are more useful. Etc.


I would love a union type in Java, but I still enjoy the language, community and especially development experience. I wouldn't recommend it for everything, but it's sturdy by design and enjoyable for when it fits.


I'd love if interfaces worked as in typescript.

As long as the object signature matches the interface as parameter then you can use it.


Check out project manifold, it’s a compiler plugin for Java. Among other amazing features it adds structural typing [1], which is essentially interfaces typescript interfaces.

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


Well hopefully it won't be mixed in _randomly_.

I have some use cases in mind and think it will be very helpful. I have been converting a 10+ year-old code base to modern, functional-style Java and believe that sealed types and pattern matching will help us further simplify our code and eliminate more usages of nullable values.

A challenge for us will be that we need the core library to stay on JDK 8 for a while. But, in general, I am finding that you can implement a JDK 8 library that works well with newer JDKs if you are careful (and willing to write more verbose code using the older syntax/libraries to support the newer paradigms)


I did title it that way at first but ended up changing it at the last second, ended up shunting it off course.


It's a great post. Thank you for writing it!


thank you for giving it a read!


The "Sealed classes" feature, as described here, just feels all wrong to me.

They are saying that if you have a (normal) interface, anyone can create a new class implementing it. So if you do

    if (x instanceof Foo) {
        ...
    } else if (x instanceof Bar) {
        ...
    } ...
then your code will break at runtime if someone adds a new class, as that code won't expect it. So the article is saying the solution is to use the new "sealed" interfaces feature, so nobody can create any new classes implementing that interface, and your "if" statement will not break.

Surely object-oriented programming already thought about that and already solved that? (I know object-oriented programming is out of vogue at the moment, but Java is an object-oriented language.)

The solution there is to add a method to the interface, and all your classes implement that. Then rather than having a massive if/switch statement with all the options, you call the method.

That is better than preventing people from extending your code, it allows them to extend your code. They just have to implement the method. And the compiler will force them to do that, so they can't even accidentally forget.

The example given of color spaces (RGB, CMYK etc.) is a great example. I can absolutely imagine writing code which uses color spaces, but then some user or client having a need to use a weird obscure color space I haven't thought of. I wouldn't want to restrict my code to saying "these are the color spaces I support, due to this massive if/switch statement listing them all, the code is written in such a way that you can't extend it".


> The solution there is to add a method to the interface, and all your classes implement that.

What if you don't know all methods that you will need in advance?

That is the problem and this problem is solved by sealed classes. However, by doing so they introduce a new problem: what if you need more extending classes and you don't know all of them in advance? Which raises the question: is there a way to achieve both?

This problem is called expression problem. [1]

There are (statically typed) languages that are able to solve the expression problem, Java is one of them [2]. However, unfortunately the way to do that in Java is (still) very complex and unergonomic, hence rarely used. Languages like Haskell or, if you want to stay in the JVM world, Scala do much better here.

[1] https://en.wikipedia.org/wiki/Expression_problem [2] https://koerbitz.me/posts/Solving-the-Expression-Problem-in-...


The solution with sealed classes also allows anyone to extend the code, but in a different dimension than the solution with an interface method.

The solution with interface method and virtual call is very inflexible when you want to add new operations instead of adding new classes. If you want to just add one new operation, then you have to go to all the implementations and add new methods. And you possibly break the implementations you don't have access to. And all those methods must be defined in a single class, even if they are unrelated to each other. This seriously degrades code readability (and performance as well - those vcalls are not free either).

The sealed class extends much better in this case. You just add a new switch in one place and done. No breaking of backwards compatibility.

This is the famous expression problem.

https://pkolaczk.github.io/in-defense-of-switch/


  > If you want to just add one new operation, then you have to go to all the implementations and add new methods.
not necessarily, if you have extension methods (kotlin, swift) you have the option to extend the interface and only override the specific implementation when needed


I understand what you're recommending, and I've seen Bob Martin talk about it extensively (polymorphic dispatch instead of instanceof), but it's something I disagree with.

To do this kind of polymorphic dispatch, objects have to deal with multiple concerns within themselves.

In a video game, a Car might have .render(), .collide(), .playSound(). Later on you can add a Dog, which also has those three methods, and you don't need to edit/recompile the Renderer, the PhysicsEngine, and the SoundEngine. And other programmers can add additional entities like this without introducing bugs into my precious code! What's there not to love?

Well, now both my Car and my Dog need to know about graphics, physics, and sound. And these entities don't exist in isolation. Cars and Dogs need to be rendered in the right order (maybe occluding one another). They'll definitely need to check for collisions with each other. And (something which has actually happened to me in a Game Jam) my sound guy is going to need to step into all my objects to add their sound behaviours.

I would much rather work in the Physics.collideAll() method, and have it special-case (using instanceof) when I'm thinking about physics, and work in the Graphics.renderAll() method when I'm thinking about graphics.

A more common example I see in day-to-day backend Java web dev: when I'm sitting in the (REST) Controller deciding how to convert my Java objects into HTTP responses, I much prefer it if I can consider them all in one method, and map out {instanceof Forbidden} to 403, {instanceof NotFound} to 404, etc., rather than putting getCode() (and other REST-specific stuff) into the Java classes themselves.


The OO solution is to have a PhysicalObject class that dog and car both inherit from (or use via composition).


Then there's even more scattering around of the logic.

Good way:

  PhysicsEngine {
    List<Entities> entities;
    doCollisions() {
      // Logic involving instanceof
    }
  }
Bad way:

  PhysicsEngine {
    List<Entities> entities;
    doCollisions() {
      // Delegate to whomever.
      entities.forEach(e -> e.collide());
    }
  }

  Dog {
    collide() {
      // What the hell can I do here?
      // I don't know about the rest of the world
    }
  }

  Car {
    collide() {
      // What the hell can I do here?
      // I don't know about the rest of the world
    }
  }

Worse way:

  PhysicsEngine {
    List<Entities> entities;
    doCollisions() {
      // Delegate to whomever.
      entities.forEach(e -> e.collide());
    }
  }

  Dog : PhysicalObject {
    collide() {
      super.collide();
    }
  }

  Car : PhysicalObject {
    collide() {
      super.collide();
    }
  }

  PhysicalObject  {
    collide() {
      // Not only do I not know about the rest of the world
      // I don't even know how *I* collide, because what am I?
    }
  }


It doesn't always make sense to allow extending -- String is `final` for a reason (and one might even argue that final should be the default, and one should explicitly mark with `open` classes that can be subclassed).

The stereotypical FP example for sum types are a List -- there you only have an Element<T>(T head, List<T> tail) and a Nil(). There is no point extending it, it would, in fact, result in incorrect code in conjunction with all the functions that operate on Lists.

Also, the Visitor pattern, which is analogous to pattern matching is very verbose and depends on a hack with the usual method dispatch semantics of Java. I do think that pattern matching is several times more readable here.


In a world of untrusted code and SecurityManagers, it was critical that strings be immutable so a data race couldn’t bypass a policy decision. The JVM doesn’t have immutable arrays, so string methods carefully protected the embedded array from tampering, and couldn’t be overridden.

Most classes don’t have this problem. The original authors of a class don’t know what I’m trying to do, and they don’t bear consequences if I (a consenting adult) get it wrong.


There are valid use cases for that.

Consider a security interface of some sort, e.g. such that validates a security token.

With a normal interface, it is easy to implement it and ignore the token (allow all), siphon off the token, add a backdoor, etc. If a class doing that is somehow injected where a security check is done, it can compromise security.

Now with a sealed interface, there cannot be new, unanointed implementations. If you get an object that claims to implement that interface, it's guaranteed to be one if the a real, vetted implementation that does the actual security check, not anything else. You've just got rid from a whole class of security bugs and exploits.


Nice article as someone familiar with sum types but not sum types in Java.

I don't know if sum types alone are enough to get me to like Java, pervasive nullability is still around and even rears its head multiple times in this article.


Nullable is a huge issue in Java, but annotation-based nullability frameworks are both effective and pervasive in the ecosystem (and almost mandatory, IMO).

I'm really excited about https://jspecify.dev/, which is an effort by Google, Meta, Microsoft, etc to standardize annotations, starting with @Nullable.


This can never be as effective as changing the default reference type to be not nullable, which would break backwards compatibility, so you can never really relax.

I know Kotlin is basically supposed to be that, it has a lot of other stuff though, and I haven't used it much.


That's basically what c# has done. But it's implemented as a warning which can be upgraded to an error. I think it might even be an error by default in new projects now.


Holy shit, how didn't I know they'd taken it this far? This is great! https://learn.microsoft.com/en-us/dotnet/csharp/nullable-ref...

They actually fixed the billion dollar mistake...


They didn't. A proper fix would require getting rid of null altogether in favor of ADTs or something similar. I work with C# daily and nulls can still slip through the cracks, although it's definitely better than @NotNull and friends.

I haven't worked with Kotlin in a while, but IIRC their non-nullable references actually do include runtime checks, so you cannot simply assign null to a non-nullable and have it pass at runtime like you can (easily) do in C#.


they won’t change the default reference type to non null. might take a few years but you can see their planned syntax here: https://openjdk.org/jeps/401


I hope they succeed. So many people have tried.


Not having nulls is easy.

Persuading Java devs not to use nulls is hard.


I was just writing about nullability annotations!

https://news.ycombinator.com/item?id=37534184


Nullable annotations don’t work with well with generics, or at least those tools I use.


With valhalla, we will have explicit nullability as well, so that problem will also be handled.


It's also not expression orientated yet.


Fortunately switch has now an expression variant. But I would die for an if-else expression (very subjective, but I would prefer a more verbose if-else to ternaries), and especially a try-catch expression!


> Why do we call them product types anyway?

Answer in the piece is not wrong, but put more intuitively and succinctly: the total number of possible value (inhabitants) in a product type is the product of the quantity of inhabitants of the constituent types.

Replace the “product”s with “sum”s and it works too.

Interestingly, the total number of unique functions (judged only in terms of input and output) from a -> b can be found by exponentiation, (inhabitants of b) ^ (inhabitants of a).


>Answer in the piece is not wrong, but put more intuitively and succinctly: the total number of possible value (inhabitants) in a product type is the product of the quantity of inhabitants of the constituent types.

More intuitively and succintly: product type is equivalent to a cartesian product of sets


Maps (and lists) are other examples of exponential types. Intuitively this should make sense that these are the same as functions because any pure function could be (theoretically) replaced with a map lookup of pre-computed values. In this context, list is a special map where the keys are integers.

Writing it out mathematically, given a List of Bool, the left-hand side is the number of elements and the right-hand side is the total possibilities.

- 0 : 1

- 1 : 2

- 2 : 4

- 3 : 8

- 4 : 16

- 5 : 32

and so on


Ill add that in, somehow forgot about that golden bit of info


I can't wait 'til Project Valhalla is complete and Java finally gets value types. Then with sum types, value types and goroutines it'll be one of the nicest languages out there.


java was never really a bad language.

the people were. if not massive over-engineering. too many abstract concepts that make it hard to grasp a codebase.

code voodoo - in the form of reverse GOTO statement i.e annotations

DI frameworks .

what needs fixing is not the language but the ecosystem. there needs to be a "reformation" movement within the java ecosystem.

yeah people migrating to kotlin or clojure or scala isn't enough.


100% agree. You can create a HammerFactoryFactory to churn out HammerFactories in any language. But the ecosystem in Java (and C# is similar imo) promotes and encourages this type of problem solving.

The one thing Java really does need is free standing (or namespaced) functions though. Sometimes I don’t want a class, what’s wrong with a function in a module or namespace in that case?


A class is a module/namespace. You can make a class just to have functions in it.


It’s not though. In lots of languages a namespace can span multiple files, whereas a class must be declared in a single file[0][1]. Modules can usually contain a collection of functions and classes. And namespaces can also contain multiple classes/structure/functions and sometimes modules depending on the language[2].

[0]: https://www.typescriptlang.org/docs/handbook/namespaces-and-...

[1]: https://learn.microsoft.com/en-us/cpp/cpp/namespaces-cpp?vie...

[2]: https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref...


The author cites this to justify the need for Records:

> Most Java objects set every field to be private and make all fields accessible only through accessor methods for reading and writing.

> Unfortunately, there are no language enforced conventions for defining accessors; you could give the getter for foo the name getBar, and it’ll still work fine, except for the fact that it would confuse anybody trying to access bar and not `foo'.

Scala supports pattern matching on objects implementing the `unapply` method.

Is this considered harmful? Why didn’t Java follow this route?


it's a matter of standardisation again. Java's standard is like C++; ponderous. The record pattern jep indicates in final footnotes that something like unapply may be in the works, so all hope is not lost.


Java was always a great language. It's the enterprisy ecosystem that make me want to throw up. To implement a line of logic, I have seen dozen classes and interfaces.


道生一,一生二,二生三,三生萬物。萬物負陰而抱陽,沖氣以為和。

The Function gives birth to the Unit type. The Unit type gives birth to the Boolean. The Boolean gives birth to the Value type. The Value type gives birth to the Top type. Each Top type contains 0s and 1s, thereby bringing harmony to the computation.


Golang is simple, smart and opinionated subset of Java/C#.

Everything moves slowly in corporate world. It will take another at least 2-3 years for large community of Java ecosystem and average devs to adopt Java 21 and capabilities.


Go is just a dumb subset hyped up as simple, but it is useless and slowly it will have to introduce all the remaining pieces in some ugly way as they didn’t plan with them ahead of time - see generics.


That is exactly what I used to think when I started with it. But I changed my opinion after 6 months with Go and eco-system. It's not ugly, it's different and much more concise.


It is more verbose than java by all objective counts.


I think there may be a “Penny wise but pound foolish” thing going on in your analysis.


Is there any widespread concern over lack of unsigned int native types?


Not really. With valhalla they can be user-implemented efficiently, although without syntactic sugar like +.


Does that mean addition would look like

  Uint32 a,b,c;
  c = a.plus(b);
in what you describe, once project valhalla is complete?


Well, that will be available as a basically zero-cost abstraction. But I haven’t seen more specific mention of it in the mailing lists/design proposals - they definitely didn’t want to add a new primitive type, but perhaps if they manage to heal this rift between objects and primitives and it will only be add syntax-level complexity, they might go for it.


the argument for Java seems to be that it's gradually gaining features we already have in Scala, Kotlin, OCAML, F#, etc... so why not use the languages that have those features today?


Inertia. There are still a ton of Java shops out there, and many of them will not switch even partially to another language anytime soon. The hope is that these orgs might find it easier to upgrade to Java 21+ than to learn and start using Kotlin/Scala/etc. I fully expect I might find myself working at such an org again, and when I do I’ll be grateful for newer Java features.

Signed, a dev who would never willingly choose Java over Kotlin for anything ever again.


One thing about "gradually gaining features" is that the Java language architects do a very good job with backward-compatibility. This means that your application/library will be easier to migrate to newer versions of the language and you will continue to have a large number of 3rd-party libraries available. The Scala community is currently migrating to Scala 3 and many libraries have not been ported (Scala experts correct me if I'm wrong.) Kotlin also seems to be less stable at the API level than Java (but I may be wrong here, Kotlin experts please correct me.)

This backward-compatibility does comes with costs (e.g. non-reified generics)

Also, if you're a library developer and you want to create a JVM library that can be used by Scala, Kotlin, etc. developing in Java is often the best choice. For one thing it avoids any dependencies on the standard libraries of those other languages.

Disclaimer: I like Kotlin and Scala, but mostly use Java.


Because Java will still exist in 5 years


I hate Java because the docker containers based on it take too much RAM. The smallest Spring JAR takes about 70-120 MB for a single container!


Hate spring instead ;)


My favorite go feature (not exclusive to go) is `implicit` interface. Once I drank from that cup there is no going back!


What a weird title change.


did that to make it a bit clearer what I was really optimistic about, but I unfortunately can't edit the hn post itself.


Does it add value types?


Thatll probably come in about a year or so going by the pace of valhalla's completion.


Is there an Ocaml vatiant for the JVM (similar to F# for dotnet)?


I know of Scala for an FP variant but nothing that's ML-like specifically.


Well, one could argue thay Scala is a ML for JVM with Java syntax


I just found this:

https://flix.dev/


You could say Kotlin to some extent..


Yeah, it has nice funcional capabilities and libraries (like Arrow[0]).

[0]: https://arrow-kt.io


any advice on a book/tut to learn "modern" java?


Manning's Modern Java in Action is outstanding. It assumes you already know basic Java.

https://www.manning.com/books/modern-java-in-action


both suggestions here are for books that cover java 8. how is it possible that java 8 is modern relative to java 21?


Modern Java in Action (the 2nd edition of Java 9 in Action) covers Java 11 and contains a final chapter that talks about "future Java" features -- many of which are now implemented.

It's a great book for Java programmers who want to make the jump to functional-style programming. If that's the transition one is hoping to make, then I would definitely recommend the book.


And I would recommend "The Well-Grounded Java Developer" (2nd edition) to those who have mastered the functional-style Java of "Modern Java in Action". It covers JDK 17 (and beyond) and also has a a polyglot JVM focus with coverage of Kotlin and Clojure.

https://www.manning.com/books/the-well-grounded-java-develop...


Core Java for the Impatient (3rd) is less than a year old and updated for Java 17.


excellent - thanks


... and I agree that there is a need for a Java 21+ book which includes discussion of algebraic data types, pattern matching etc.


Core Java for the Impatient by Horstmann.


In other news, we have an upgrade path at work to go from Java 8 to Java 11 pretty soon.


We (finally) upgraded last year from 8 to 17. Lots of nice things, I’m so glad we were finally able to. But it was a big project due to some dependencies and how some things were done internally.

I can’t wait for 21, but I’m not sure when we’ll switch. At least it will be trivial compared to leaving 8.


do/i/still/need/to/create/sub/directories/for/the/things/i/want/namespace?


Only if you're using vim like someone from the 70s, otherwise any sane IDE handles those things for you

But, yes, namespaces are still mapped to paths, it's not like they rewrote the JVM to use blockchain or something


>Only if you're using vim like someone from the 70s, otherwise any sane IDE handles those things for you

I don't think that's quite it when Ruby is also from the 90s and figured out not to tie namespace resolution to the directory tree


I am not sure why this is a problem. Looking at the organization of the resources directory of every project I've ever worked on, I am not sure I want more opportunity for physical and logical concepts to unnecessarily diverge.


I mean, its the same idea as custom path config in TypeScript


Let's not hold ruby up as a poster child for modularity.


Yeah I really like my IDE doing stuff in the background without me knowing shit about it.


Yeah, I do like my IDE doing the tedious work for me. It's also really nice to unzip any JAR I need to troubleshoot and know exactly where to navigate to find exactly the file with the code I need.

I like Golang a lot but the standardisation and verbosity in Java-land has some benefits after you get used to it enough to learn to ignore the boilerplate, it's pretty good to have some kind of consistency enforced by the VM when working in large codebases.


Yes some C# codebases end up with wildly inconsistent namespacing as you can do anything you like


I don't understand how it is doing stuff in the background.

You literally ask it to move some class and then it does it. And it's very obvious what it's doing.


Then you’d be thrilled to hear about all the stuff your NVME drivers are doing in the background without you knowing shit about it. Or maybe you prefer to engrave your ones and zeroes in stone by hand?

Abstractions exist for a reason. In Java, that abstraction is coding in classes and packages, not files of text.


better_than_having_just_a_single_file_that_have_lots_of_functions_without_namespacing


Or import ../../../../../asd


Yes/you/do/but/the/ide/does/it/for/you/so...


The/ide/giveths/the/ide/takeths/all/this/could/be/easier/and/you/know/it.


Yeah, just like Python, Ruby, JavaScript.


Ruby modules aren't tied to the file system


True, but they are loaded by path.


We made the jump from 8 to 17 this year! Some of our developers of course still write 1.6 level code, of course


Going from 11 to 21 is a lot easier.


Depends- one of the hardest parts of the 11-20 upgrade for us was that cms gc was removed.

If you run a bunch of different microservices with distinct allocation profiles, all with high allocation pressure and performance constraints, and you've accomplished this w/ the help of a very fine-tuned CMS setup, migrating that over to G1/ZGC is non-trivial


Java sort of suck for microservices (microservices suck on their own) as it has relatively high bootstrap cost.

High allocation rate feels weird with micro services - I suppose that depends a lot on the coding style. G1GC is meant for generally large setups, with several cores at least. E.g. the default setup of 2048 areas on 2GB heap means, allocations over 1MB require special care.


If you GraalVM Native Image or one of the frameworks based on it then bootstrap cost disappears:

https://quarkus.io


GraalVm is great, it made our Spring REST API app go from 10+ seconds to 0.5 seconds on startup (not to mention the lower mem and cpu requirements).

Except… when we try to build it with Jenkins on a Kubernetes cluster, this happens: https://github.com/oracle/graal/issues/7182


I can't help but think if you're teetering on a knife's edge, only holding on thanks to hyper tuned GC params, then you should take a step back and consider getting out of that predicament.


Is that what happened at your company?


Yup. We've clearly benefitted- G1 and generational ZGC have large advantages over CMS- but it's a lot of experimentation and trial-and-error to get there, whereas other deprecations/removals are usually easier to resolve.


Isn't the default config correct for pretty much all workloads, unless you have very special requirements? Like, at most one should just change the target max pause time in case of G1, depending on whether they prefer better throughput at the price of worse latency, or the reverse.


What were the backward incompatibilities preventing that move? I thought Java was pretty backward compatible, but I've heard this a lot.


It's more likely that there's some dependency that is not easy to upgrade, because the upgrade also includes api changes (from the dependency, not from the jdk).

Compound this with multiple dependencies that exhibit this issue. If you have a "legacy" application that does not require active development, there's zero business incentive to invest into the upgrade. Unless you could prove value in having the upgrade, it just doesn't get prioritized.


Java has in recent times actually removed some apis from the standard library, and made some inaccessible (undocumented internal apis especially) so any advanced frameworks that use them would have been affected.


7 -> 8, but JBOSS 6 is a pain to migrate away from.


8 to 17 for us


Try with 8 --> 11 first then 11 --> 17


we are going ahead to 17. too much resources spent to do an intermediate step


Sad this gets to front page while a blog post which documents that .NET 8 has 200 A4 pages worth of performance improvements gets absolutely ignored.

C# keeps being the language people are looking for but don't know about.


It's because C# only "recently" became cross platform in a reasonable way.

Hard to overstate the mindshare that it lost for that


And it's still not as cross-platform as Java is, or pretty much all other tech stacks. FreeBSD and commercial UNIXen are not supported at all. If you prefer to develop on macOS or Linux (hi there), you don't get access to many Windows-only tools (mostly for debugging and performance measurement). Microsoft only cares about other operating systems to inasmuch as you can host the resulting application there, but really expects you to develop on Windows. Which makes sense... for them.


For Linux and macOS you can use Rider and CLI tooling (as expected for most other langs), VS Code is up there but obviously does not come with profiling or advanced debugging tools.

The bias does exist but it's going away, especially that now a lot of developers are moving over to M-series macs.


Since it is a Java thread, I’m gonna mention that ecosystem-wise, the JVM is just much much bigger and of higher quality - you very often see copied JVM packages in C# with much less features, less stability, and often even being paid.

With that said, it is a cool language and platform, that is indeed underhyped.


This 100x, C# and Java are also very similar programming languages. C# may be a better language, but the difference is minimal. Most C# developers claiming that C# is a better language often don't know Java. I remember one C# guy telling me that Java didn't have closures before Java 8, which was not quite true. Java developers had access to Groovy which had closures. Groovy may be another programming language, but it has always had 99% Java syntax compatibility and they both compile to the same byte code and work seamlessy together. Groovy 4 also has LINQ if you like that. And using Groovy is no harder than including it as a dependency in Gradle/Maven.

Java eco system is so vast, and of top notch quality, with excellent documentation available for free. It enables businesses to move faster, at a pace most other languages cannot offer. It's not perfect, especially I find data sciene libraries lacking in Java compared to what the Python eco system offers. But depending on the task I try to choose the right tool for the job, not the same language for every job.


Groovy is... not Java.


I think it is, just as Gradle is Java. Groovy is a superset of Java, and makes some parts of Java development better. I for example write many tests with Groovy or implement DSLs for my clients with Groovy.


I've written in C#. It's Java. If they're not the same language to you, you need to learn more languages.


Its a good language, but the corporate sponsor and community is suspect. Microsoft has a long history of user hostile actions. The types of companies that use C# often treat SWE as second class. Learning and using C# limits career options to low salary, high stress jobs.


I'm going to vouch for this comment but not because it is correct but rather it presents good opportunity to address the concern.

C# and .NET are most heavily invested in by Microsoft which owns and steers its development, that is true. It is also true that JVM world sees investment from multiple MSFT-sized corporations.

And yet, despite the above, it keeps moving forward and outperforming Java on user experience, performance and features despite being worked on by much smaller teams. I think it stands on its own as a measure of a well-made technology.

In addition, you can look at source code and contribute yourself, 90% of what makes .NET run is below. Almost all development happens in the open:

    https://github.com/dotnet/runtime
    https://github.com/dotnet/installer
    https://github.com/dotnet/roslyn
    https://github.com/dotnet/aspnetcore
Could Microsoft do a better job at making it even more community-facing and attempting to make the .NET foundation as a sole owner and steering committee of the language itself? Sure. But it's not that bad either today. Quick reminder - Oracle is not exactly a saint, perhaps even worse (MSFT has never gotten into any litigation even remotely related to .NET or C#).

As for career opportunities, as other commenters would note, this is highly specific to a region and does not translate globally. Again, we are discussing the "how good the language/platform is" first and foremost. I don't see startups adopting Go because of the market or trusting Google not to rug pull them...so perhaps we can do a better job so the next language of choice they pick is C#, which has much higher ROI in the hands of the good developers (for example, it can be very easy to adopt as a second language if you are well versed in Rust).


I don't have as much experience or knowledge as others in this thread, but just taking the opportunity to chime in that C# is a great language, and pretty pleasant to work with. And it was a language to which I was introduced as a 24 year old at a venture-backed startup, not at like Boeing or the company Dilbert works at or something.

It has features like LINQ. It has routinely had a lot of good updates, new features always coming out, increases in performance, etc. The primary thing lacking has been good support for developing and deploying on Linux, and a couple other devops-related things. It's a shame to see that holding it back, but at least MSFT actually opened up Linux support at all (nobody thought they would).


See https://isdotnetopen.com/ for why people think .NET has openness issues


Microsoft uses C# internally and has a vested interest in making it better for all of its own developers, as well as making it good to increase industry adoption so it has a larger pool of skilled developers to hire from. Your arguments about who backs the language don't hold water.


Does it have a vested interest to make everything work cross-platform?


Well, Linux has won the cloud meaning it is and will be a primary target for .NET.

I'm not sure what vested interest MSFT had in some of the changes introduced for iOS in .NET 8, but now it can be targeted with NativeAOT too, so there is clear investment and dev effort both from community contributors and MSFT employees to support various platforms.

Somewhat arbitrary example, there is also a project to support RISC-V (https://github.com/dotnet/runtime/issues/84834).


For both reasons mentioned above, yes. Even internally, many teams in Microsoft are shipping containers on Linux for prod services.

Azure is also the big moneymaker, so these days they want all their technologies to have Linux support since that's the dominant cloud OS.


Both are horrible languages and while Java libraries are abundant, most are horribly over-engineered, archaic stuff from the 2000s, where everything was about forcing OOP on every problem. Log4j executed code from an ldap server. Insane.


We live in bubbles.

In my limited 6 years of professional experience as a software engineer, I have never met a single person who writes C# or .NET.


Second this. But here in hacker news, C# is hated for no reason, just the old Microsoft mantra and NET Framework


> C# keeps being the language people are looking for but don't know about.

Every time I point out about C# most programmers are saying about evil Microsoft and being locked in the ecosystem. Most programmers I've met do not even know that .NET Core is MIT open-source and runs on any device.

I really wish C# would be more widely used because it is amazing, and for sure 10 times better then Java.


It’s because C# is only really nice on the surface. As soon as you actually do something with it that takes it beyond its cozy CRUD, it becomes such a nuisance to work with.

We have an ODATA client that we build our selves to use in our React frontend. Now, this would obviously not have been something we also used on the backend if we knew what we know now, but it’s the perfect example to illustrate my decades of experience with C#. We had the client because we use a lot of Office365 and BC36 APIs and they are ODATA, and since we had the client we figured we might as well use ODATA on our internal APIs. Which was all well and good until we tried mixing EntityFramework, ASP versioning and ODATA together. These are all Microsoft packages mind you, and they just don’t work together. They each use the Model builder magic that C# comes with, but they each use it differently enough that things become a nightmare. Which basically sums up why C# has been a terrible language for its entire existence.

If you never get to those breaking points, then C# is fine. But when you use it for what you’d assume it was intended for, well… At least the ODATA experience sort of triggered my C# PTSD from back when I wrote an admin system to maintain all the thousands of school printers and computers in a municipality, which was hefty Active Directy work and an absolute nightmare to the point where I literally had to either extend or rewrite half the Microsoft packages because they either weren’t finished or outright terrible (again likely because they weren’t finished). Similar to how the ODATA, ASP versioning and EntiryFramework packages aren’t really finished right now. I mean, I can look at the road map for EF and see some of our issues as planned fixes on who knows when. Anyway, since those days more than a decade ago, it’s become obvious that if you want to do anything AD related then you need to use PowerShell and not C#, really, C# doesn’t even run in Azure runbooks and Python does so it’s obvious that Microsoft themselves don’t really use a whole lot of C# for that part of IT operations internally. And I think it’s frankly the same with a lot of things. The ODATA package seems to fall in that same “not used by Microsoft” box. Because it appears to be some sort of “fork” that’s mostly maintained by a single employee in China.

So yes, if you just use regular ASP and EF without versioning or ODATA to do very standard CRUD monolith APIs it’s a language that’s both fast and productive. Sure you’ll still need to do all sorts of silly things with your Azure DevOps pipelines to get EF migrations for Microsoft’s own SQL server to work, but hey, you can. But if you’re actually going to be doing things that takes it beyond it’s “basics” then no, C# is really not the language people are looking for but don’t know about.

I’m also partly in the “Java is sort of 10 years late with 21” club, but it’s not like anyone has switched away from Java in those 10 years, and really, it’s not like it was a fun experience to go through the .Net -> .Net Core -> .Net, .Net Standard and .Net Core -> .Net years, so maybe Java did it right?


Your issue seems to be with packages provided by Microsoft, not C# language itself. Model builder magic is part of those packages, not C# language. These types of libraries often make simple things quick and easy, but complex things (or just simple customization) much harder or even impossible.


I've seen this response before, but why on earth would we be using C# if not for its included batteries? I can't imagine a world where we would have chosen C# to write an API unless there were some specific benefits, and while I may be wrong to assume this, I don't think I've ever met someone who would. Not so much because there is anything wrong with C# or .Net for that matter, it's okish, it's just well... Honestly, writing C# is a lot like writing typescript with bad linting rules, at least to me. It's not that bad, but it's also not something I'd ever really want to do.


I agree with you that the choice of language should not be based solely on the language itself, but also on the ecosystem it provides for the task you need to complete.

I was just pointing out that your critique was about the included batteries, not the language. Every language has some bad libraries in its ecosystem.


The point I was trying to make was that C# has nothing but bad libraries in it's ecosystem. I've worked with quite a lot of languages and I've never experienced anything like it. Well, obviously Node has an "interesting" environment, but the flip-side is that it's very easy to work with what isn't there. C#'s libraries are like half-charged batteries that you won't realize are only half-charged until it's too late.


Are you feigning ignorance about why people don’t like Microsoft?

We all know C# exists


The problems with Java can't be fixed by adding new things, you can't undo decades of ecosystem development, training, and ideology built on top of the idea that inheritance is really good idea and belongs everywhere.

edit: I will say that as a Java developer I am grateful every day for the improvements to the language. Java is a very impressive language and I have a lot of respect for the people working on it.


I rarely see inheritance used in practice in java code bases. Except where I would use a union type or sealed class in other languages anyways. I don't feel what youre describing is a real issue.


You must be really lucky then. In my previous job, inheritance and abstract classes were everywhere. Coupled with dependency injection frameworks that "worked like magic", it was really hard to follow the code inside that big, monolithic app. It made me never want to work with Java ever again.


I've never been a big fan of dependency injection. It solves a problem with unit testing in Java, sure, but the reality is that Java could have done some nifty things to help alleviate that as well.


Agreed. Java injection frameworks are opaque and accomplish what they need to with overly powerful mechanisms because of the nature of the language. You don't see that sort of nonsense in python.


Wtf, so what do you think Django is?


I'm curious to understand what dependency injection is in Django that you're referring to?

I know pytest does DI with fixtures, and trying to figure out what you're pulling into a test can be difficult.


In django you're importing a default cache, a default storage etc, and write your code to the interface. In settings.py you wire it all up. It's basically the same, for testing you would have to mock the import or provide some implementation.


technically that's not dependency injection, but a global service locator.


Yeah I agree on that here.

The user doesn't write DI code for Django Class Based views. E.g. the view doesn't accept a Database upon instantiation.


Nothing to do with the nature of the language, but with the nature of the program.

If you're writing a few line script, you don't need a DI container. Once your program gets large, it becomes extremely messy without one. It's no surprise projects like [1] exist.

[1] https://github.com/ets-labs/python-dependency-injector


That DI library is not pythonic at all.

There's nothing wrong with this at all, say:

    class MyClass:

        def __init__(self, my_dep1=None):
             if my_dep1 is None:
                 self.my_dep1 = get_my_dep1()  # Or potentially raise
             else:
                 self.my_dep1 = my_dep1

             [...]

Then to test:

    def test_my_class(): 
        mocked_dep1 = MagicMock()
        my_class = MyClass(my_dep1=mocked_dep1)
        [...]


And if you want to configure the scope of `my_dep1` (singleton, transient, etc.)? What about nested dependencies? etc.


Why do I care about any of that while writing python? Those seem like artifacts of a Java based DI system.


It's a reality of any non-trivial program, regardless of the language.


No. Some languages don't require you to play cat and mouse games to get around artificial limitations put there by the language designers.

There was a talk at PyCon a while back about that patterns commonly used in Java were non-existent in Python.


If that’s the only thing it’s used for in the project, then you are most likely good with Autowired (in Sping) or something similar.

Any complicated DI solution must have a reason for it being there and I’ve seen too many projects complicating themselves on buzzwords like DI or MSOA without really needing either that much


Sounds like you’re confusing DI with DI containers.


I've seen the same craziness with NodeJS, and very simple Java services with minimal inheritance.


Inheritance used to be extremely common, look at AWT/Swing - however more or less it finished there, e.g. more than 20y back.

There are still lots of folks who love 'protected' and deep hierarchies, of course. There is stuff like spring that uses way too many interfaces with a single implementation, doing something a bit off the regular road ends up implementing tons of the said interfaces anew.

However the 'hate' part is mostly the internet (well esp. Hacker news)warrior topic


Great to hear. I used to program java in 2010 and inheritance was still beeing heavily used back then. But things change, if both the lang and the main community has change to focus more on simplicity its def worth looking at again. Coroutines and pattern matching is really good features


Contrast that, I've seen numerous Java codebases, young and old, and inheritance is very much one of the core ways that people program.

I strongly suspect that in a few cases some Java devs using net new systems and avoiding common frameworks will perhaps be able to avoid lots of inheritance but I find it insane to say that that's common or even easy.


Inheritance is heavily used in Java in 2023, at least in projects I’ve had to look at.


> I rarely see inheritance used in practice in java code bases.

That seems absurd to me and I have a hard time understanding it, honestly.


> I rarely see inheritance used in practice in java code bases.

Without some specific call-out, it can be assumed that this is a very niche viewpoint that has no bearing on modern development.

The vast majority of projects use inheritance, today.

Does it use Spring? extends SpringBootServletInitializer

Meaningful responses using values from a request? extends OncePerRequestFilter

Formatting exception handling responses on web requests? extends ResponseEntityExceptionHandler

Then there's all the interfaces you have to satisfy, because it's all tied into the standard library and popular libraries.


You need to inherit to create anything that is a class. But the focus is on composition. You inherit from useful classes so you can build the solution using composition.

I don’t like modern Java because there’s too much non-Java magic code. Layers of stuff that “helps” but removes me from the language. Entire programs written in config and special text wrapping classes and methods. How it works requires understanding multiple intersecting languages that happen to be strung together in .java files.

Edit: when something is in config it’s not checked at compilation. Every environment from dev to prod can have its own config so when you compile in dev you don’t know what’ll happen in prod. I know: let’s add more tools and layers.


> You need to inherit to create anything that is a class

That's true of the Java compiler, for which the documentation is strewn around the intenet...but an example is here: https://medium.com/javarevisited/compiler-generated-classes-...

The Java syntax doe not require extending Object explicitly.eg This is a valid, useless, class:

    public class App {}
> I don’t like modern Java because there’s too much non-Java magic code

It seems like this is the common path for popular languages. They develop their own library-backed DSL's for the most common use cases, which are often little more than macros (@data @getter, @notnull, etc). I am biased by what I've seen in the last 30 years though.


The OP/GP wasn't really complaining about inheritance, despite the fact that this is what they wrote.

The OP is complaining about the difficulty of the API because it is _exposed_ through inheritance.

The complaint about `SpringBootServletInitializer`, for example, is exactly this. There's nothing wrong with inheritance. In fact, SpringBootServletInitializer is exactly what you want to use inheritance for - because you need to build your app using the servlet api.

There are http servers that aren't servlet api, such as https://vertx.io/docs/vertx-web/java/#_re_cap_on_vert_x_core... , which uses little to no inheritance (since the api surface is smaller).


Having a single level of inheritance (especially if it's an interface) is not a problem at all. Having deep inheritance trees can be a code smell.


While you have to extend some things in Spring, yes, the whole point of the DI in spring is so that you can often compose instead of extend.


If you type everything with interfaces in your codebase, you are much less tied to inheritance. In fact, everyone could be written to be composed.

However Java doesn’t support type union so you can get into some ugly and verbose situations but the JVM doesn’t really check type so this is more a compile-time issue and could be fixed in a future Java language revision.


>However Java doesn’t support type union so you can get into some ugly and verbose situations

Isn't the "sealed interface" described in the OP blog post a type union? Or you mean anonymous unions?


Definitely referring to anonymous unions too. Without them, there’s still friction sometimes which makes union types unnatural.

I haven’t written Java in a while and I can’t remember if you could sometimes fake a type union using a generic type on a method, but if you can, it’s definitely super ugly and would raise eyebrows during any code review.


Anonymous unions are a relatively rare feature; Rust and Haskell don't have them, for instance.


Sorry, what? Is not java toghether with Ruby maybe the most hardcore OOP enthusiasts that uses inheritance for "most stuff".

Has the java culture move so far the last decade ?


Effective Java, written by Joshua Bloch, and first published in 2001, had a very influential section called Favor composition over inheritance:

https://books.google.co.nz/books?id=Ra9QAAAAMAAJ&q=inheritan...

Why do you think DI is so prevalent in Java codebases? It's a great way of simplifying composition.


>had a very influential section called Favor composition over inheritance

The GoF book (Design Patterns) had the same, somewhere early, like in the Introduction, in 1994:

https://en.m.wikipedia.org/wiki/Design_Patterns


I’ve been writing OOP code and reading about it for a decade or two, and inheritance was identified as problematic pretty quickly. The problem today, imo, is more underuse now.


>inheritance was identified as problematic pretty quickly

Exactly what I referred to here:

https://news.ycombinator.com/item?id=37539443


Yeah, it’s a bit like how the school version of lisp makes people think real lisp code bases are all lists and recursion.


Funnily enough, it actually started from C++. And, Java is so large that it is simply meaningless to talk about a unified style -- sure, some Java EE behemoth will continue to run on a complicated Application Server for decades still, churning on its workload, but so will someone write a Micronaut microservice, or something for robotics, and these will all have distinct styles.

With that said, even the Java EE/Spring complicated word has been moving towards a less inheritance-based future.


Even in Ruby, IME, deep inheritance is fairly uncommon.

Wide inheritance through mixins is common, though.


HtmlElement. AST. Socket. Stream. Window. Closable. Runnable.


Your Point?

Long:

Please make your Point Understandable.


Examples of classes where inheritance is indispensable. I cannot imagine what kind of programming you are doing where that pattern never comes up.


There is nothing wrong with inheritance. But sure, there is usually something wrong with deep inheritance or using it, where better solutions exist.


No, there's a lot wrong with inheritance. It leads to all sorts of issues and while theoretically one can keep the tree 1 level deep, in practice it's too tempting to expand the hierarchy.

This is one of Java's big issues. The other is reference equality as a default, pretty horrible. Records help but records are also limited in when they can be used.


"in practice it's too tempting to expand the hierarchy."

Not anymore, if you got stuck, by doing that once too often. But 2-3 levels can be allright as well. It depends, what you are doing amd with whom.


> No, there's a lot wrong with inheritance.

No, there is a lot wrong with bad code. No need to blame the language or any programming concept.

Note that I say that for all programming languages, not just Java.


That's a difference between a dangerously unsafe tool and a good tool. By unsafe I mean providing enough of footguns to shoot yourself in the foot. Java has a community of people that indulge in teaching about inheritance as the first thing after classes in their "OOP" lessons. This ingrains the habit in beginners.


> By unsafe I mean providing enough of footguns to shoot yourself in the foot.

Just to play on the comparison: would you say that a gun is a "dangerously unsafe tool"? I would tend to think that guns have been very well optimized over time, and I don't know of a gun design that prevents me from shooting myself in the foot. Actually that would be a limitation of the gun.

But we all agree that people who use guns need to learn how to use them properly. Why doesn't this apply to programming languages? How did we as an industry end up in a place where it's considered the norm that developers don't really know what they are doing and need some kind of child safety in order to not hurt themselves?


Yes, guns are dangerously unsafe, they're designed to cause harm. It's hard to reconcile that goal with the need for safety. That said, guns do have safeties - guarded trigger, safety switch, drop safety, etc. [1].

The general principle is to design in as much safety as possible without compromising the intended purpose of the tool too much. The degree to which this is possible varies. Inherently complex and/or dangerous tools like guns, cars and aeroplanes require significant training before safe operation is possible. Most tools however can be made perfectly safe to use for anyone without any special training. Like plugs and outlets [2].

Programming languages are no different. The history of PL design is defined by the ever increasing restrictions placed on what languages let programmers do in the pursuit of safety & correctness.

But really, we humans, especially programmers, have no clue what we're doing and need all the help we can get.

[1]: https://en.wikipedia.org/wiki/Safety_(firearms)

[2]: https://youtu.be/139Q61ty4C0?t=42, https://youtu.be/139Q61ty4C0?t=105, https://youtu.be/139Q61ty4C0?t=205


It seems you conflate the tool and the education about the tool?

Why is Java itself bad, because some people (maybe) teach it wrong?

Also inheritance as a first lesson with OOP is not bad either, if the follow up is sound. But as far as I know, the concept composition > inheritance was already taught 15 years ago.


If we look at other modern programming languages, some don't even have inheritance. Lets take Rust for example. The language from the get go avoids the trap of deep inheritance hierarchies, by ... not having classes and inheritance! Instead it has structs and traits you can implement for the structs. Behavior separated from structure. They learned from the mistakes or design flaws of the past. Sure, Rust is not perfect, but this aspect about it I really appreciate. I am sure though, that someone somewhere will implement something resembling inheritance and create footguns anew.

Lets compare with Java. Java has forced everyone for decades to shoehorn everything into classes (sometimes an enum, sometimes an abstract class, whatever). Last time I checked it was still impossible to simply open a file, put a function (!) inside and be done. No, Java forces you to wrap that into a class or similar construct, as if a function in itself was not enough and not self-sufficient. There is a whole mindset behind this, that seems to come from an ideological "everything must be a class, because then I can instantiate and then I haz objects and can call methods". Other languages don't need classes to have objects.

Decades fast-forward. Java learns, that lambda expressions are a nice idea. Java will offer structs. Java learns, that lightweight processes are very neat to have. And despite all that, the old footguns still remain and could only be undone at significant cost, because of backward compatibility. This is where programming language design sins really rear their head. PHP suffers from the same problem. Horrible standard library, but cannot be fixed, unless you break backward compatibility.

On one hand you can state, that it is all on the programming, who is "holding it wrong" or needs more education. On the other hand, the programmer can choose a better designed tool, that doesn't cut their fingers every time they try to use it. Just because it is possible to do a good job with Java, that does not mean, that Java is a good tool for the job. It means you can only let the most experienced people work with the tool, instead of what we have now, every Billy knowing Java, writing classes and getting a kick out of inheriting from a super class.

Good teaching will avoid weighing things one should avoid disproportional. There is no good reason to teach inheritance early on. It should be a thing taught on the side, something one quickly glances at and says: "Yeah, that also exists, but lets not get into that much, as we will not need it much ..."

But in Java you probably can't avoid it for long, because you will want to make use of some library sooner or later and library authors might force you to make some class inheriting from their library's class, define a behavior and pass that in. But often that is not enough ... No no no, you need to pass in a factory for classes, that implement an interface, and their methods will implement the actual thing. I have seen this recently for logic of checking, whether a password is valid/acceptable. Why the heck do I need to implement a factory for that, when the actual task is simplest logic, checking whether the password has all required kinds of characters in it?

Usually this is completely overblown, because you want to pass in some behavior, that could be expressed as a simple function. I shouldn't need to create some brimborium. All I should be required to do should be to implement the logic in a function and pass that as an argument to the library.

The existing ecosystem forces its "OOP" on you. If you are not willing to throw out decades of ecosystem, which actually is the main advantage of the JVM, then you will need to deal with the cruft that has been created before.


Ok, you don't have to convince me that Java has many problems, as I intentionally avoided Java for some of those reasons for the last 15 years. But thanks for reminding me of some of them ..

But the concept of inheritance I like. And I use it succesful allmost daily. And like I said, I am aware of how you can use it in the wrong way. And my code surely is not perfect either, but the flaws I have, do not come from inheritance. Those parts are actually very clear, solid and stable. And still flexible.

And Rust is trendy I know. I have never used so far, so it was news to me, that they don't even have inheritance, but to convince me, that Rust has the superior concept, I would like to see it to be used as widely as inheritance languages first.


[..] a programming language designer should be responsible for the mistakes that are made by the programmers using the language.

It's very easy to persuade the customers of your language that everything that goes wrong is their fault and not yours. I rejected that...

- Tony Hoare


I feel like it often goes in the other extreme: "if a 2 years old cannot use it, it's too hard".

I am fine with requiring that professionals know their tools, even if I'm well aware that this is not the norm.


You don't like code reuse?


Love it, and love using plain ol' functions for it


Inheritance is not necessary for code reuse.

All you really need for that is named procedures. A way to combine modules and different files certainly helps too.

Type hierarchies can even hinder code reuse, because you can’t just pick the stuff you actually need.


That question seems blatantly disingenuous.


Composition > inheritance.


Please ELI5 what is wrong with inheritance and/or how Java have it wrong. Do we need to go back to a new object oriented undegraduate course? Genuinely asking.


"Prefer composition over inheritance" is actually a well-known mantra and appears in, for example, the Design Patterns book from 1994, which is basically the OOP bible. And this is within the OOP bubble, you won't even find inheritance outside of it.

https://en.wikipedia.org/wiki/Design_Patterns

Don't take undergraduate OOP courses seriously.


> a well-known mantra

which isn't an explanation, but just the same as the OOP mantra that was taught in undergraduate courses.


One reason is the unstable base class phenomenon. A some changes in the base class require you to constantly change all your derived classes.


"What OOP users claim" -> https://i.imgur.com/KrZVDsP.png


Inheritance is fine. It helps to avoid code duplication for logic that requires encapsulated data (private, protected fields).

The problem is what if you have a class that needs to derive behavior that's in two (or more) classes? Multi-inheritance is terrible because it becomes a nightmare of which class overrides which.

If there's a shallow level of inheritance (1-2 levels deep), then there's nothing wrong with it. Things like composition for most use cases has been common advice since forever.

What happened with java was that there was a massive movement for enterprise code that way over-complicated everything based on ideas that didn't pan out. There were all these auxiliary patterns, and ideas that people had to learn to be onboarded onto projects, and so many people poorly understood them that it led to even more spaghetti code, too many people that developed using dogma instead of common sense.


With compositions A uses B but B can never use A. With inheritance Child can use the Parent, but Parent will also call the Child (virtual methods) which in turn can call the Parent again etc.., so the code can become difficult to follow. It can become very complicated with multiple inheritance and multiple levels.


Such code would be difficult to follow regardless of whether you use inheritance or not. Sometimes, two pieces of code developed independently just need to interact very closely with each other.

Remember that OOP and inheritance to some extent came out of the need to develop GUI systems. Inheritance is still heavily used in GUI toolkits because it's a good fit for that problem space. You have graphs of objects that need to be treated at different levels of abstraction, and controls often need to customize (override) or implement some behavior that shouldn't itself be a part of the public API.

Attempts to get rid of inheritance and OOP in UIs end up looking like Compose or React. I found very quickly when working with these that pure composition just wasn't sufficient and these approaches have their own issues; problems that OOP trivially solves become difficult to impossible to solve cleanly without it.


I've certainly done that in the past, but not since I changed jobs ... 7 years ago. Probably not for several years before then.


Inheritance provides "is a" relationships between classes. At a time when people would spend months designing their software upfront, building big diagrams of classes, etc, this was not so bad. You'd have a very clean system design that maps directly to your class design.

The problem is when things change. A simple example - you build a classification of all life and it is built upon the idea that everything "is a" plant or animal. And then one day it turns out there are fungi. This is not a fun situation to deal with and inheritance makes it a lot harder because the "is a" relationship is driving a ton of your logic.

IDK I don't want to get to into it beyond that, many people have written quite a lot on the topic.


> you build a classification of all life and it is built upon the idea that everything "is a" plant or animal

With all due respect, this and similar examples are just plain wrong, and I really can't take anyone seriously when that example is used. The point of programming, and its abstractions is to help you complete a task, and make the implementation maintainable and easy to reason about. I think grabbing the "is a" part is fundamentally bad -- there is no point in creating a taxonomy in and of itself, this is no database for that data. Inheritance sometimes is the correct abstraction and while it is definitely overused, when it's correctly applied there isn't really another abstraction that would fit better. E.g. see a graphics library's Node class as the stereotypical correct application.


I think your post can be broken down into two main points.

1. That my point is bad for some reason

2. That inheritance is sometimes a great tool

We agree on (2). Inheritance is pretty amazing, even if I think that it's ultimately a terrible feature to build so ingrained into a language and to expand in power to such a degree.

As for (1), I don't really get your point. Abstractions help you complete a task - ok. Abstractions are to help you reason about stuff - ok. Something about "is a" being bad? None of that really explains why my example demonstrates the problems you run into when you try to build a classification of values using inheritance. But I also said that I wasn't going to really try to explain much, it's been written about plenty.


> This is not a fun situation to deal with and inheritance makes it a lot harder because the "is a" relationship is driving a ton of your logic.

Judicious OOP design allows others to change behavior based upon needs that perhaps the original coder never thought of.

I would not call most Java OOP design judicious. The control in Java is owned by the library writers and the language devs -- not the people using it.


What is a language with judicious oop design? Asking to know


I tend to be fond of Python OOP styles.


This problem is partly a type system limitation however.

In a more flexible type system with union types and other magical features, your example problem would be less of an issue.

However Java has an extremely limited type system so there is no middle ground between composition and inheritance. Once you choose one way, there is no “middle step” to migrate over.


Union types are very rare (scala, typescript are the notable ones I can think of that implement it), most other languages only have sum types (which includes java as well, see sealed interface/classes), where you have to create each unique set of types you want to use separately, wrapping each option into a marker type basically.


In cases of refactoring, inheritance seriously sucks.

But it is quite advantageous when, as you say, your model is well thought out and stable.


If you want to see how codebases work without inheritance in practice, I suggest checking out Go, which features polymorphism through interfaces, but not full-fledged inheritance


Prefer composition over inheritance has been a common mantra for at least a decade, if not more. Maybe you should undergo the last decade of development and training.

There will always be shitty devs, and since java is one of the biggest languages, it definitely has more than some ultra niche academic language no one uses. I don't think it is the fault of the language though, or if that somehow were a reason to choose a different language. Should a decent BMW driver sell their car, because plenty assholes buy that car also?


Just because everyone has been saying "composition over inheritance" doesn't mean that that's how things get done. Jump into any Java codebase and you're 99% likely to see inheritance used as one of the primary abstraction mechanisms.

> Should a decent BMW driver sell their car, because plenty assholes buy that car also?

A better analogy would be "Should you drive on the street where all of the shitty drivers do donuts and street races?"


> Prefer composition over inheritance has been a common mantra for at least a decade, if not more. Maybe you should undergo the last decade of development and training.

What are some other well known mantras? The null reference is a billion dollar mistake? Minimise mutability?

Maybe the language designers and library writers could catch up too.


Records and value/primitive classes are immutable, not to mention https://openjdk.org/jeps/401


Inheritance is a useful feature, using bad abstractions is a user error.

Nulls are an error in that no language feature solves them (though third-party tooling does), so far at least.


Yeah, I think this is the part that gets me. To be fair, I think well designed Java is flexible in exactly the right ways for an enterprise development. The trouble is that enterprises don't typically pay well enough to get people who are really good and the popular ways of building Java applications are not great.


> ideology built on top of the idea that inheritance is really good idea and belongs everywhere.

Those are two different ideas.


I used the word "and" to indicate that, yes.


I think a comma would make it less ambiguous.

Starting that single though with "The" indicates to me that you don't think those are two different ideas.

Compare:

    the idea that inheritance is really good idea and belongs everywhere.
With:

    the idea that inheritance is really good idea. and belongs everywhere.


I think that would actually be an erroneous comma?


> This set of changes allows Java to express one of the foundations of functional programming that the language never could before - Algebraic data types, along with idiomatic ways of using them.

And here I thought the foundation of functional programming was functions, which Java still doesn't have.

Seriously, functional programming is about functions, not types.


> functional programming is about functions

different people mean different things when referring to functional programming.

Some people believe that functional programming are using higher-order functions like `map`, `reduce`, `forEach`, etc, which takes a function as a parameter, instead of doing imperative loops.

Some people, in addition to above, believe that functional programming is about creating functions that take a certain 'shaped' parameters, to allow for automatic checking.

And lastly, the "real" functional programmers are people who believe in referential transparency in your functional program.


How is Math::sqrt not a function in Java? Sure, it’s called a static method and it lives in a class - but is it meaningfully different from the same being a function in a Math namespace in say, C++? You can static import it even and use it as a function. This is just needless hair splitting imo.


> And here I thought the foundation of functional programming was functions, which Java still doesn't have.

> Seriously, functional programming is about functions, not types.

Well it does have methods and it does have "Functions" (not to mention "Bifunctions", whatever those are). And there's certainly nonsense around exceptions and referring to outer variables. And no currying.

But if I understand you correctly, you real complaint is about not having effect-free functions, right? But then it becomes about the type system again, because that would be the mechanism to prevent effects.


Can you convince me to use Java? I’ve never used it (only C++, Python, CL) but it feels bloated and dirty.


None of the memory problems of C++, the speed of C++ most of the time. Thousands of high quality libraries, a JVM that is a marvel of engineering after 20 years, tooling for development, monitoring and introspection of exceptional quality. Many of the internal tools at the largest Cloud developed in Java. Most enterprise level software. NASA extensive use of Java. A team behind the development who has stunning common sense in resisting the latest fads. Support for IEEE 754 that took years. AOT compilation...The infra behind most internet banking sites out there. Use in high speed trading.

What else do you want from the ecosystem?


What would be a modern IDE for Java?

I remember programming Java in Eclipse. And it was powerful for the time, but everytime I read bloated, I automatically think Eclipse since then..



and also FOSS (Apache 2): https://github.com/JetBrains/intellij-community (as well as PyCharm found in the "python" subdirectory)


IntelliJ is very good.


Eclipse has seen vast development in the last few years and is fast and easy to use these days.


Another rather new option is VSCode + the Java plugin


>” None of the memory problems of C++”

I’m sorry, I’m calling BS here. You can still leak memory in Java.


You can leak memory in any language. That’s an impossible bar. The problems op is referring to is obviously about memory safety.

Java obviously isn’t thread safe like Rust is, but it’s typically safer than C++ on that front too.


Rust isn’t thread safe either. It’s borrow checker will attempt to correct you but you can still run Rust unsafely.


As I understand it Rust's unsafe is designed so you can encapsulate unsafety behind a safe interface.

It's up to the programmer to verify the safety of code using unsafe. But it they do so correctly then you can rely on the borrow checker to verify that everything else is safe.

This means that when you run into a thread safety bug in Rust code you should only have to look at the unsafe blocks to find the culprit.


With that said, I think it is important to mention that if you go off the safe path in rust and you do hit such a bug, your current execution is no longer trustable, it could have corrupted the heap, introduce UB, and die with a segfault, etc.

In java, race conditions can enter illegal application state, but their scope is much more limited. NPEs are 100% safely handled, etc. you can only get off the safe road with using Unsafe unsafe, and manipulating the JVM’s inner state which is not even allowed by default. Depending on application, this difference may matter a lot!


There are two kinds of leaks IMO. The one where your algorithm allocates gigabytes needlessly (and cannot be avoided by language design) and more insidious stuff like having to unsubscribe from an event handler for your object to be dereferenced.


Shitty code is shit in any language. What's your point?


Isn't that true in any language that lets you allocate memory?


var list = [];

while (true) { list.add(1); }

Here you are, which language won't leak memory here?

Also, which language will let you connect to a prod instance without a performance hit to get some stats on the heap and its allocated objects? Hell, you can even list every instance of a type as I've recently learned.


>var list = [];

> while (true) { list.add(1); }

idk, I don't think endlessly growing memory usage is really what a leak is, a leak is really when you have no way of accessing the allocated memory to free it (ex. dropping the last remaining pointer to a `alloc`'d block in C) or the opposite in garbage collected languages: accidentally holding onto a strong reference to objects that should be freed.


> the opposite in garbage collected languages: accidentally holding onto a strong reference to objects that should be freed

So basically what I gave an easy example of. Sure, it won’t look like this in practice, you probably accidentally keep adding to a list, or a cache, but basically this is what happens. The former kind of leak can’t happen with tracing GCs.


You can. But you have to try rather hard.


> None of the memory problems of C++

Every time I see something like this I roll my eyes... C++ doesn't have any "memory problems".

There are sometimes human problems, such as thinking one is capable of coding without understanding the (basic programming) concept of a pointer. But that's because the human's dumb, not a language problem. (This argument also sometimes comes from those who do understand basic programming, but are only familiar with C++98.)

> Thousands of high quality libraries a JVM that is a marvel of engineering after 20 years

I reluctantly have to agree. :)


> Every time I see something like this I roll my eyes... C++ doesn't have any "memory problems".

You can choose which report you would prefer: Microsoft's, Google's 65%, ... I'm sure they just hired bad developers that don't understand pointers.

Sure, human problem, but if no human can use the tool correctly, then surely there is some problem with it. And no, that doesn't mean that memory unsafe languages don't have a place, but we really should have a very good reason for going down that road.


I understand pointers, yet I can’t be trusted never to make a mistake with them. “Each mistake will go uncaught and become an unpredictable catastrophe” is a language problem when your target audience is made of meat.


"But that's because the human's dumb, not a language problem."

If everything is the programmer's fault for being dumb, then Brainfuck is an excellent system language.


> C++ doesn't have any "memory problems"

Well, every time I see someone claim this, I roll my eyes.


It must be hard being perfect. The rest of us will keep looking for solutions for mortals.


I’m a fan, long time fan, but I can honestly say that I feel Java is one of the most pragmatic development systems on the planet.

It can do most anything. It can do it most anywhere. And you can code in it using several different paradigms.

It’s easy to install, it’s easy to be instal several versions, the footprint, by today’s standards, is not enormous.

It’s easy to deploy, especially server side. While you can certainly do all of that modern stuff with containers and what not, it’s not really necessary for Java. Drag and drop a JDK install, anywhere. Plonk your Java server code, anywhere, and point it at your JDK, and. . .that’s it! Fire it up. The JDK is remarkably light on dependencies.

And since most Java apps are “pure Java”, the apps rarely have a dependency outside of the JDK. And since Java apps routinely bundle their Java dependencies, the apps don’t stomp on each other either. Even SQLite bundles it’s binaries in the Java Jar file. So wherever that jar goes (again, and typically bundled with your app), SQLite goes. No floating dependencies.

Desktop bundling requires a bit of search engine research, but it’s doable. And the runtimes can dwarf something like Electron installs.

As a language is Java ELEGANT? Not particularly. It has its own elegance in some areas but that can break down or get overrun in others.

But, boy howdy, it sure is practical. The ecosystem is so huge. It compiles really fast. I marvel at the stories folks tell about their dev cycles in other languages. How do they get anything done, besides sword fighting?

I love Java, but I’m very comfortable in it. But the Maven based dependency system works, it’s huge, it makes use and reuse simple. The IDEs are powerful and mature.

And, finally, Java’s not dead. Hardly. Oracle has been a surprisingly good steward (with warts, as always). The language has been on a rocket of development and shows no sign of slowing down. Server side is still very hot with all the frameworks and all the different approaches. Things like GraalVM taking the JVM in a whole new direction.

And, yea, that. I’ve only been talking Java the language, not the JVM itself per se. That’s a whole other thread.


Throughput in Java can be quite high, and latency can be quite low. You still occasionally get latency spikes from the GC, but these days that's not so bad. (If you are extremely latency sensitive, stick to C++).

Dependency injection frameworks are your bread-and-butter in server-side Java, and some can take a while to grok, but Java can be very productive after the chosen framework "clicks" for you. Typically, this means you'll be writing constructors or factory classes that construct your dependencies per request. The way you wire your factories into the system differs by framework, but it often involves using Java's annotation syntax.

Not having to worry about memory management is a huge win for productivity over C++. Likewise, constructors in Java are much more sane than in C++.

Classical object-oriented programming is intuitive, and Java tends towards the "one obvious way to do something" paradigm. I find it pretty easy to hop into legacy code bases, if I already know the framework being used. The collections API in the standard library is one of the best classical OO APIs out there.

The JVM provides great visibility into the performance of your system. A lot of instrumentation comes "for free".

I'm not sure where you get the "dirty" and "bloated" feeling from. By any definition I can think of for those words, Python would be in the same bucket.


> Java can be very productive after the chosen framework "clicks" for you.

you can chose to not use DI frameworks at all..


DI buys you maintainability.

Without DI, there's too much flexibility in how objects get constructed. If you want to add new functionality to a legacy code base, it can be difficult to track down the different integration points and slow to plumb through your dependencies. These projects can turn into spaghetti very quickly.

DI solves this with a simple recipe: define your functionality, define your dependencies, wire it up to the injector.

The pattern is useful in all OO languages, Python and C++ included.


the problem is that those DI frameworks are all adding substantial amount of complexity and brain load.

I started using just static factories in my code, and abandoned all those DI and it works well enough.


  > I started using just static factories in my code, and abandoned all those DI and it works well enough.
i have done this as well (though not in java), it makes knowing what gets initialized how and where much easier and faster to debug


Yes, I agree that Python can feel “dirty”, although not necessarily “bloated.”


urllib... getopts...

There's as much useless junk in Python's standard library as there is in Java's.


What's dirty is trying to get a python project reliably running on all dev computers. Pyenv, venv, wheels, suddenly you need a complete rust or c toolchain etc. With java I haven't had those kind of issues the last decade, it just works. With python it's still a big hassle and everything breaks every update.


To be fair, getting all those scientific library binaries compiled and installed was a huge pain even before python came around. Python's toolchain made it better, but only marginally in some cases.


Sadly this isn't just the case for scientific Python. E.g. some widely-used web library (I forget which) recently switched to using a Rust implementation of TLS, and suddenly you needed a working Rust tool chain available to install it. This caused a lot of grief.

I love Python, and I've been experimenting with Rust and enjoying it so far, but this situation could have been better handled, I think.

I agree 100% about scientific Python though. It's a whole new level of horrible when it comes to dependency management.


Java itself is a nice language that can be lean and mean but has long been culturally bloated by early tooling and conventions. Since Java 8 (~10 years) the tide has reversed and it now mostly accepted to write terse code that is to the point.

The language gets updated regularly and is managed very competently. Although it may seem to trail on some aspects vs other langs, it benefits from second mover advantage - new features are done right, for the right reasons.

Any Java code ever written is essentially eternal, both in text and compiled bytecode form. Compiler and VM compatibility guarantees are unmatched. You can stumble upon 20 year old code and just use it.

The platform is mature and robust. The JVM itself is a marvel of engineering.

The ecosystem is extremely rich. There isn't a problem that wasn't addressed by a library or a stack overflow question.

The tooling is unequalled. IDEs can reliably perform large code transformations because there is no preprocessor or macros and the type system is relatively sane.

If you know Java you'll never be out of a job.


Bloat is a meaningless word. One person's bloat is another's essential.


How could you possibly like C++ but feel that Java is bloated?

Like I academically understand disliking Java but this just makes no sense.


I think people are understanding 'bloated' differently.

I agree C++ is bloated in a 'just make it a setting' / 'add this feature' / 'kitchen sink' sort of way.

I agree Java is bloated in a boilerplate, empty directories, maybe except for another directory, 'oh god the boilerplate' sort of way.


> I agree C++ is bloated in a 'just make it a setting' / 'add this feature' / 'kitchen sink' sort of way.

Interesting. Can you expand on this, explain more? I honestly don't know what you mean or are referring to -- and I'm a heavy modern c++ advocate -- but suspect if I did it might expand my mindset/viewpoint a bit. :)


Honestly I haven't actually used it since C++14 was new, but it's a common complaint IME that each 'edition' (as Rust would call it, I don't know remember the term, standard?) adds too much, there's too many ways of doing things, variations of pointers, boost, and so on.

I think a lot of people wish it had stayed at say C++7, a mature C superset, but that's enough now. I probably should have said 'I understand' rather than 'I agree' - I don't feel strongly, I don't use either of them.


Ohhh, I get what you mean now, thank you! I understand (and mildly disagree). :)

Yeah, C++ is a living language, I can respectfully see that bothering some people. I viewed c++98->c++11 as essentially a new language with c++14 and c++17 being "bug fixes" for the "new language" that is c++11. But certainly, it can require more learning about every decade now.


I think of Java as more bloated than C++ at runtime - with C++ I can make smaller executables that start up faster, use less memory, etc.

But if you are talking about the source code itself, then things are quite different.


???

I'll answer your question with a question: Have you seen https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris... ? :)

I'm guess that to those of us who remember when Java came out, "FizzBuzz: EE" is what we think of when we think of Java. :P

In Java I have to type a bazillion characters to get anything done! And make all these useless directories and files and InterfaceClassFactoryProtocolStreamingSerializer BS. And worry about how that executes.

C++? No bloat*, just speed

*Yes, there's some _optional_ bloat. But compared to Java? no contest.


> In Java I have to

You have to do literally none of these things to write working Java.


When java came out? So mid 90s c++ vs mid 90s java?


I mean...Java is run through a VM while C++ is compiled to native code.

I can see that being used as a reasonable argument for bloat.


> run through a VM

This is just incorrect use/understanding of Java’s execution model. It does have a runtime, but it is definitely not a VirtualBox VM.


What are you talking about? No one brought machine/platform virtualization into this conversation other than you.

The bytecode interpreter for Java (and other similar languages) is literally called a Virtual Machine, due to the way it functions:

https://en.m.wikipedia.org/wiki/Java_virtual_machine

It would seem your understanding of the Java execution model is flawed.


C++ allows for much more terse code than java when you do small projects. It is popular in competitive programming for that reason, not just because it runs fast but also because it is really fast to write algorithms in it.


You don’t have to use Java. All the languages you’ve mentioned are fine. If you think that using Java to solve a particular problem is a good idea, then you should definitely use Java.


What feels bloated and dirty?

Because there is plenty of bloated and dirty stuff in C++ and Python too.


What convinced me to consider Java just recently is GraalVM. I don't know about the "dirty" part, but at least GraalVM might solve the "bloated" part. Compiling to native standalone executables makes a big difference to what Java is traditionally known for (portable bytecode but requiring the JVM). Been using C++ and C# for a long time now, and my pet peeves with these languages were also only just addressed relatively recently (C++ getting modules and native AOT for C#). Java being able to get natively compiled makes it worth considering among these. Now, for me, it's a matter of what other frameworks/libraries available for the respective languages would get the job done while I only have to rewrite little to switch among them if I try my best to stay within their syntax overlap.


I work on a Python codebase now after years of JVM and here's what I miss, static typing taken as read.

* Threads that can use more than one core.

* The best approach to package management I've ever seen, no-one worries about typosquatting or "someone already registered all the cool crate names" because every package has at least two coördinates - a group id based on a DNS entry you must be able to prove you have access to, then the package name. So I can publish my malicious package gauva, but as I can't register it with the group id "com.google.guava" or even "com.google.gauva", no-one is going to mistakenly use my package.

* No fucking dance of the C libs. Or Rust these days. I want to use a Python package that wraps a C lib, but there's no binary wheel built for my version of Python and/or platform arch, so build from source it is. Which I'll figure out when pip install -r breaks, and then I get to a) apt-get/brew/etc. install the lib and/or headers b) install any compiler toolchain needed buy not yet present c) export compiler flags and header locations d) all of the above, and then run my pip install again. Until the next library breaks.

* Intersection types for generic bounds. I don't need it that often but when I do, I really miss being to say that for `def foo(ex: T) -> Z`, T must implement interfaces A and B.

* Logging. Occasional horrible security holes aside, Java's logging story is one of the best I've ever worked with, compared to it, Go's sucks, Python sucks more, like how you can't set your preferred timestamp format AND include millis without writing a custom formatter (seriously, go look at the source code of logging.Formatter.formatTime...)

* JMX and MBeans. Having detailed VM performance metrics and the ability to change exposed values or execute an exposed method at runtime built into the VM is amazing. Want to have a quick look at what's happening with the GC? JConsole in. Want to set one logger in particular to DEBUG to see what's going on in prod without having to restart anything? JConsole or jmxterm and away you go. Want to experiment with your batching parameters under prod load to find the sweet spot for throughput? Expose them via an MBean.

* No need for pyenv, virtualenv, etc. Although being fair, if you're working with different incompatible JDK versions, you'll usually end up using SDKMan like Python uses pyenv, and Maven needs a little bit of additional configuration to locate your toolchains, but Gradle is smarter about finding your available JDKs.

* Libraries with type signatures, makes for far easier code reading, and if you pass **kwargs 5 levels down, I hate you. Yes you, sentry-sdk...

* Libraries with correct type signatures, looking at you confluent-kafka, either publish type stubs to typeshed, or update your goddamn doc-string type hints so my IDE doesn't think that confluent_kafka.Message.value() is missing a parameter.

* The ecosystem. I really really really miss the ecosystem of JVM FOSS packages.

* The speed, in general.

I'm sure there's more, but these are the pain points I've been having of late.


Id rather say use kotlin. It comes with a better type system with explicit nullable types, proper closures, and syntactic sugar to easily enavle beneficial programming patterns.

Nd it is very compatible with Java itself, interop is dead simple


Finally Java catching up with some basic features that Scala has for 10 years or so. Hoping that more good stuff from Scala will get into Java soon, maybe at some point I can try to use Java again. :-)


Big firms I have worked at still using Java 8.

Dead.


We have an app that also needs to be able to run on Android, so we are held back by the shitty Java Google implements for Android.


You can use [limited] Java 17 with Google's Android. The compiler converts Java 17 -> Java 8 bytecode.


I agree that many companies are “stuck” (through their own choice really)… but at least Java is moving now.

In many ways Java’s boringness is a feature that keeps it easily maintainable, at a low - easily recruitable - skill level.


At least you can now run Minecraft 1.7.10 on Java 17 - https://github.com/GTNewHorizons/lwjgl3ify


What a marvel of engineering.


A lot of Apache projects still use Java 8 :(


Has anyone used virtual threads? I tried to migrate my app over to VT's and kept experiencing random deadlocks and I couldn't figure out what was causing them. I tried moving all the synchronized blocks to reentrant locks, but that didn't work. I also tried turning on the TV debugging system properties, but none of them printed the problem.


[flagged]


As someone writing code on Java since v1.0 and making tech strategy decisions on the C-level, I consider myself sane and experienced enough to say that Java is something that will do the job well and can be the primary choice for startups and new projects. Kotlin as a platform is not mature enough to deserve the same qualification.


> Kotlin as a platform is not mature enough to deserve the same qualification. With (1) a lot of Java backend projects migrating to Kotlin (let's be negative and assume 20%); (2) all of Android running Kotlin, which is around 80% of the world's mobile phones; (3) the world's best IDE authors building features and improving Kotlin; (4) the language being almost 13 years old at this point; and (5) Kotlin being 100% compatible with Java in both directions, I wonder what additional maturity would you be looking for. Can you point to a specific problem?

I mean, of course, Java has been here since forever and we've made careers out of programming for JVM. I'm wondering if you are referring to a specific maturity issue, or just want to highlight that Java was here for longer... if it's the latter, I don't think it can be a valid argument against Kotlin or in favor of Java.


Hard agree on the maturity, it's clear this person has literally just decided that Kotlin is immature despite it flying in the face of all facts


The job of software engineers is to translate a business domain into software.

Different languages are better or worse at modeling different domains of problems.

I have seen the same problem solved in different languages take 2-3x the amount of work and have a large difference in ongoing maintenance costs.

Java's reliance on traditional OO means that only a small subset of business domains naturally map to the language's constructs. People get good at modeling problems in Java, but that doesn't mean Java is the right language to model all problems in.

A problem that is best represented as stateless functions processing data is a miserable fit for Java, a problem that is best represented as thousands of threads going off to do independent work and then unifying results, is a bad fit for Java.

Sure Java can be used, and I'm sure as an experienced Java developer you instantly thought of how to model those problems in Java, but experienced users of languages that map naturally to those problem domains don't have to think of how to translate to their chosen programming language, because the language is a natural fit for that particular problem domain.

As a trivial example, I once saw Java used to parse a raw binary stream that had a lot of unsigned ints in it. Miserable usage of Java, tons of wasted space. In a language like C/C++, or even C#, it was possible to just directly map a structure onto the raw byte stream, but for Java they had to read each unsigned 32bit INT into a 64bit INT to ensure no overflow.

Horrible mismatch, lots of unnecessary allocations, ugly code, wrong tool for the job!


> Java's reliance on traditional OO means that only a small subset of business domains naturally map to the language's constructs.

In my career I worked on a really wide range of business domains and have not even noticed the problems you are talking about.

The problem of mapping certain types like unsigned int that you mentioned is rather rare integration scenario. There’s not that many domains where numbers with the most significant bit are important, but MAX+1 size numbers are not.


> In my career I worked on a really wide range of business domains and have not even noticed the problems you are talking about.

How many other programming paradigms have you explored in depth and built solutions with?

Back when I did OO in Java and C# I didn't think twice about it, converting things to an OO paradigm was second nature, but once I stepped outside my comfort zone and learned of other modeling techniques, I realized that I had been jumping through extra hoops to get to where I wanted.


> I have seen the same problem solved in different languages take 2-3x the amount of work and have a large difference in ongoing maintenance costs

I am sure none of this experiences were basis for an objective experience, there are too many variables. But surely, there are (very few) problems that doesn’t fit Java too well, though it is not an OOP-only language, it is a multi-paradigm one, with many FP concepts seeping into it.

E.g. for some ultra low-lat audio processing I wouldn’t choose it, your binary stream processing may be another good example, though it depends on a bunch of factors and in many cases java is more than fine for that as well.


> reliance on traditional OO means that only a small subset of business domains naturally map to the language's constructs

You don't have to stick to OO constructs. Java has functional programming support and pattern matching.

> best represented as thousands of threads going off to do independent work and then unifying results

Java has had executor support for a long time now. And if you're talking about IO bound tasks, Java has virtual threads now.

> parse a raw binary stream that had a lot of unsigned ints

Java has `Integer.parseUnsignedInt()` and other similar operations.


I'm sure you have done great work in Java, as have many of us, and for many of those years, Java really was the best tool for many jobs. But that was then, now is now.

I'm not saying Java doesn't work, even for greenfield projects today. It works fine. But me and many others also think it's not enough for something to merely work, especially not when there are other much better and more modern tools. (which Java is demonstrably playing catch-up with)

You and every other C or higher level exec need to get a grip, get with the times and realize that continuing to insist on Java is only going to make (or, by now, keep, more like) your organization stale and attract more and more mediocre engineers for every year that passes. I know because I've worked with the kind of engineers who are still doing Java + Spring like it's still the 90s, and they're not the ones you want on your projects. The best talent don't even want to consider working with Java if they can help it.


> especially not when there are other much better and more modern tools. (which Java is demonstrably playing catch-up with)

Which modern tools do you have in mind here?

Java does indeed playing catch up with new features. That's inevitable for a language came out 30 years ago. OTOTH many other languages are still behind Java in terms of the runtime environment and the ecosystem. You should pick what works for you, not to go with the trend.

> The best talent don't even want to consider working with Java if they can help it.

I think you seriously overestimate how talent those devs are if language is the deciding factor of what they choose to work on.


> Which modern tools do you have in mind here?

We're talking about Kotlin here (there are many contexts where neither Kotlin nor Java is the correct tool, but for pretty much any use case where Java is an option, Kotlin is going to be a better one)

> not to go with the trend

I'm so sick of this argument, it presupposes that Java is the default and anything else by definition is just a trend. Kotlin is not a trend, Kotlin is not a fad. Stop begging the question.

> I think you seriously overestimate how talent those devs are if language is the deciding factor of what they choose to work on.

Ok, and I think you seriously overestimate the willingness of people who have given Kotlin a serious try and seen first hand how much it offers over Java to go back to working for a Java-heavy shop if they can help it.


> OTOTH many other languages are still behind Java in terms of the runtime environment and the ecosystem.

Ecosystem doesn't matter except at the long tail. I've never needed a library that didn't exist for NodeJS, until I started doing LLM work.

Now 80% of LLM libraries exist for NodeJS, 100% of LLM stuff has a Python library, and Java libraries are incredibly rare.

Want an ORM solution? Java has you covered.


To build a good product you don’t need a kid with Hollywood star complex, for whom the choice of programming language or tools matters that much.

If Java repels such self-proclaimed “best talent”, good, because the team will be healthier without them. There’s enough engineers willing to learn and write a good code on Java to solve a business or an user problem, and they are absolutely not mediocre. There’s even no such thing as a scale on which you could put an engineer to measure some “greatness”. Every person is unique and has talents that may be useful in different types of projects.


> To build a good product you don’t need a kid with Hollywood star complex

Agree 100%.

> for whom the choice of programming language or tools matters that much

Programming languages do matter. Serious, mature professionals critically evaluate and compare tools and pick the best one for the job. Kotlin is a) not some fad b) objectively better than Java for pretty much every imaginable use case. Kotlin avoids entire categories of Java defects https://proandroiddev.com/kotlin-avoids-entire-categories-of...

Me and others who advocate for more modern tools are not self proclaimed wannabe rockstar devs who are only interested in the latest fad. Ultraconservative diehards who refuse to learn anything new since the 90s are the ones who need a reality check, not us.


As a mature professional who stopped counting learned programming languages and frameworks long ago (I’m familiar with Kotlin too), I can tell you that Java is a modern tool by all measures. It may lack some features of other languages and platforms, but it is not features what defines relevance for modern applications. Your advocacy is important, we of course need progress and testing new concepts. Yet what ultimately matters is business value and user experience. Java is pretty good in delivering it and many professionals choose it for that reason, leaving new stuff to pet projects at home. It is not diehard conservatism that lets us make this choice, but a mere pragmatism. For me it is not obvious that Kotlin is better. There are risks associated with less mature platform that already costed one of my teams a couple of wasted sprints. Productivity and quality impact isn’t really noticeable (the biggest factor in those two measures is always communication, not tools).


> it is not features what defines relevance for modern applications

what? zero-effort null safety out of the box alone is immeasurably valuable

> leaving new stuff to pet projects at home... risks associated with less mature platform

I'm so sick of this argument, it presupposes that Java is the default and anything else by definition is just new, a trend, a fad. Kotlin is none of those things and you know it. Stop begging the question.

> For me it is not obvious that Kotlin is better.

I mean, if you can familiarize yourself with things like https://proandroiddev.com/kotlin-avoids-entire-categories-of... and still conclude there are no obvious benefits then I don't know what to tell you, it's clear nothing is going to convince you


That feeling is mutual. I very much prefer boring Java or .NET developers who are all supposedly stuck in the 90s to hipsters chasing after the latest JS fad of the week.


Me too! But you're building a strawman. Kotlin isn't a JS fad. It's been around for many years, is the default language for Android platform and the de facto standard IDE for Java, backed and used by Google, JetBrains and many many others. But sure, everything that isn't what you've been doing since the 90s is a fad.


> I've worked with the kind of engineers who are still doing Java + Spring like it's still the 90s, and they're not the ones you want on your projects

And I've worked with programmers who use the latest fad of the day (golang, etc) and they're not the ones you want to have on your projects.

> The best talent don't even want to consider working with Java if they can help it.

Not in my experience.


Every single person who's arguing against Kotlin in this thread is resorting to the argument that it's the latest fad of the day. It's not.


I was mainly arguing against golang. As for Kotlin, given the incredible work that has been done in Java recently, the argument is that there isn't much reason to switch over anymore, due the introduction of more moving parts into the project (another dependency, compiler, room for issues, etc.) and the fact that Java ended up with the superior approach (better/fully fledged pattern matching, virtual threads, programmable string templates, etc.).


If I look at the job market in my area it’s dominated by Java. There are a lot of .Net jobs as well but they are typically at companies you really don’t want to work for, so it’s basically so much easier for you to find a nice job if you’re into Java.

I’m not, mind you, but maybe 21 will actually change that. I guess it’ll depend more on the tooling than anything else. Because the things I dislike the most about current world Java is that it’s very dependent on 3rd party “addons” to become a nice working experience. Like, you’ll probably want to use Quarkus if you’re doing anything enterprise related, but then you’re putting yourself at the mercy of Big Blue. I’m not sure that Java21 will change that, if it will then it’ll probably be a world where Java dominates even harder than it already does.


> the sane members of the Java community have long since moved on from Java, and they're not coming back

We wish! We're stuck in the Java job market, reading Java praise pieces like this... Oh boy! Java's getting structs?


How long until we get continuations in Java? Maybe another 10y? How long until we get TCO? Lets say 20y?

And in the end it will still not feel clean or as neat as other languages, that had this stuff for multiple decades.


Hopefully never. There's a reason no primarily functional language ever became mainstream. The "elegant code" they allow is never as lovely as the zealots imagine


None of those are a necessity, and they can be easily implemented in an additional layer (e.g. annotation processor if you really wanted to).


No language features above machine code are a "necessity". Java is not a "necessity". "OOP" is not a "necessity" either. Why don't we all work in C? Or maybe all in assembly language? Yet most of us are glad we can work at a higher level. Probably no single language feature is a "necessity", yet when they come together they make for higher level languages and elegant expression.


Let's not forget that the JVM still does not have reasonable generics & value types. The .NET CLR introduced this in 2005, bettering the JVM. Java was in a sleepy slow release mode back then. We've been waiting 18 years for value types and proper generics - Valhalla has been just about ready for a very long time. Some of us moved on a long time ago.

Agree that Kotlin is a bright spot in terms JVM languages. For my taste has adopted some of the best concept of Scala, but without the burgeoning complexity.


C# def brought a lot to the table vs Java but I feel like it has also kind of stagnated and not realized the potential of F#. Scala likewise is very powerful but def overly complicated and difficult. Kotlin + Arrow hits the sweet spot, at least for me.


As a Kotlin dev you should still be excited by virtual threads in Java 21. You'll never have to use coroutines again.


I'm very curious to see how this works out in the Kotlin ecosystem, given the amount of async code out there (and the associated function coloring issues).


You can easily combine the two, just use a virtual thread and when you hit a `suspend` function use `runBlocking` to get rid of the suspension.


Well when Sun imploded and Oracle stepped in, Java stopped getting regular releases for 10 years. Java versions literally came out every 2 years before Oracle happened.

So that’s why they’re 10 years behind. But now they come out like 2+ times a year so they’re on track making major progress.

Also the null thing hasn’t been an issue for over 10 years. Just use null annotations and both your static checker and your IDE will catch every NPE.


> Also the null thing hasn’t been an issue for over 10 years.

I don't think I've ever seen a Java codebase that doesn't experience NPEs in prod, and that includes modern greenfield ones that make use of all the tools to combat it.

> Just use null annotations and both your static checker and your IDE will catch every NPE.

Yeah, or just catch it in code review, or just xyz...

Or, just switch to a compiler that doesn't allow it in the first place, out of the box?


> I don't think I've ever seen a Java codebase that doesn't experience NPEs in prod

Frankly, the last NPE I saw in production was many many years ago.


same, Kotlin doesn't compile if you try to deference null pointers


TBH, Sun was already excessively conservative with language features in Java long before the Oracle purchase.

The JVM runtime though is and was a miracle of engineering. Enough to make the mediocrity of the language worth it. Though these days I don't (and don't want to) work in it, I absolutely understand why companies could make the choice to do so.


[flagged]


Having worked extensively in Java, Node, and Python, I’ll take the JVM ecosystem absolutely any day of the week.

Me and my catheter will be over here delivering actual software while you figure out how React 32 broke your transcompiler.


No idea why you are comparing Java, which is BE, to React, which is FE.

I think I will check Java once they finally make coroutines… I mean “virtual threads” a stable feature. That actually looks exciting… being able to parallelize almost like in Go 5 years ago.


>finally make coroutines… I mean “virtual threads” a stable feature

Don't you mean fibers? Anyway, I'm pretty sure it's targeted to JDK21.


Fibers are now called virtual threads


Except I’m over here serving 10x the traffic with 10x less cloud spend on CPU and Memory for hosts.


In what language would that marvel be? I give you the less memory....


Go has goroutines which have better cpu and memory than threads which are commonly used in java.

Java is now catching up with go and adding virtual threads which in september 2023 will have the same ease of use as go had in … 2007?

But as I wrote above, it migh really get me to check java again.


Well is it correct that until 1.14 (so 2020) Go used cooperative scheduling? Because Java Threads, (before the new virtual threads) are preempted by the OS...Go was launched in 2009? Who is doing the catching up?


Maybe you should upgrade from Java 1.4...


> delivering actual software while you figure out how React 32 broke your transcompiler.

Say that to enterprise software systems still running on Java 7 or 8 without any clear path to upgrade because their whole systems will break.


It’s almost like there is even a CS law for this exact scenario.. but Java 8 will be around and will still work 10 years from now, plus it is not an insurmountable task to bump it up to the latest version at all. Tell me literally any platform that has a better backwards compatibility story, because honest to God there is simply absolutely none.


Java 8 will be out of support in 7 years. It might seem like a lot but for enterprise systems it's nothing.


Which gives you a lifetime support of 16 years for a single version, with an absolutely sane and doable upgrade path.


C++. And it’s hated for the same reasons.


Do you feel good about yourself, talking down on people that made a perfectly valid choice by developing software in Java?

Let's look at the alternatives you mentioned: Rust and Golang. Java compared to Golang is a much more expressive language. Java compared to Rust is a much easier language because you have a garbage collector for the majority of the cases you don't need the manual memory controls that Rust offers you. In other words, Java is a perfectly valid alternative to both Golang and Rust with its own tradeoffs.

You try to paint a picture of Java not being cool but at the same time I see "modern" companies that are held in high esteem for their engineering prowess using Java to do incredible thing (e.g. Netflix and Google)


Google invented Go.

Netflix uses Java and Rust and Go.

Twitter used to use Scala.

GitHub uses Ruby on Rails.

EpicGames uses C++.

Instagram uses Python.

Everybody uses something.

Now, if you have building on top of your organizations previous 10 years of engineering, you’re probably going to be writing Java and it’s probably going to be complicated. I’ve been there. I wrote Java for 15 years. I won’t anymore. I’m in love with the simplicity and speed of Golang. I’m in love with the robustness and correctness of Rust. I’m in love with docker pull scratch:latest and putting your binary inside to run on an empty metal container. No need to waste 250MB of ram on a JVM. Use nano size instances and it’s just as fast as c5’s (async based workloads).


> Google invented Go.

A handful of people at google invented golang is the more correct thing to say.

Yet, google continues to use Java at a much much bigger scale than golang.

golang is simplistic, not simple. Once you work on large golang code bases you'll see the challenges and messes it causes.


I work on really large go codebases


You're trying to make the point that the only companies using Java are doing so because of legacy. That is demonstrably untrue. Netflix and Google have had ample time and capacity to replace Java, yet they didn't.

As any good engineer, people working at those companies recognize that Java, Golang, Rust etc. are all tools and these tools have their place in different scenarios.

I used to be like you, but my personal "anti-language" was PHP. I would talk down on organizations using PHP and I would take them less seriously as engineers. But I have come to see that they too are using a language (PHP in this case) as a tool to build a business. And it serves those orgs well apparently.

Don't get me wrong, Java would not be my first choice in many cases. At the company I'm working for, we only use Golang and Python in the backend and Typescript in the frontend. That doesn't mean that I cannot recognize the value the Java can bring and the niche it occupies.

I'm actually pretty amazed by how far Java has come as a language. 10 years ago, I thought Java was going to be replaced by the likes of Kotlin or even Scala. But Java has pretty much caught up in terms of features and ergonomics compared to the Kotlin and occupies a comfortable spot now as being an expressive and productive language without bringing the learning curve and footguns of Scala.


Manual memory controls? What are those? Rust does not have "manual memory controls". You write the code so that the borrow checker does not complain. End of story.


Those are pedantic semantics. I assume you know what I mean. But I'll explain it to you: with Rust you are in control of how memory is managed. Indeed through the borrow checker.


So which is the real programming language one should use in the God's year, 2023? (Will it change in 4 months also?)

> virtual threads but you still have threadpools and os threads

As opposed to what, quantum entanglement threads?

> No one really enjoys debugging your call stacks

Frankly, debugging has probably the very best tooling around the JVM -- so, what exactly is your chosen favorite that would be supposedly better? Or is objective reality not hyped anymore?


Yeah, I dislike Java more than most but picking on its debugging story is laughable: it’s tooling around that is excellent


I just picked up Java (via Kotlin) for the first time in four years, during which I've been doing Rails dev. I needed an AWS Lambda for zipping files into and out of S3. I banged my head against broken/unsuitable Node packages for doing so before finally giving up, since it is the lingua franca of Lambda.

I was able to rewrite and deploy the function in Kotlin in two days' time. It was easy to set up and run, and worked literally first compile and run - a benefit of static types.

Now, would I recommend it to anyone who is unfamiliar with the JVM ecosystem? That's a harder sell. But the stability of the ecosystem and a modern set of saner tools and libraries have made it much better to work in; I'd even call it pleasant.


AWS Lambda is the once place where you should never use Java. You have specific thresholds for how fast your function responds, jvm prevents that and requires more memory.

AWS Lambda’s documentation shows how to accept a Request and send a response. You don’t need a package for that. For making zips from s3, again, AWS’s sdk. It’s one of the most commonly asked questions on SO. Here’s one implementation for you that could have saved you days:

https://stackoverflow.com/questions/38633577/create-a-zip-fi...


Trust me, I looked up dozens of references. All would mysteriously halt after several files - known issues with how the Archiver library works, and none with any kind of fix, basically a data race within the library itself. Node's streams and promises are far too complex for far too little added benefit, and leads to broken concurrency in a multitude of libraries, and I'd had enough.

As for startup time - the latency isn't that important and would be an order of magnitude less than the ZIP construction, so the JVM warmup delay is actually just fine. The cost will be slightly higher, but it's not an operation I expect to run with high regularity - it's only on-demand for a reasonably small userbase.

As for complexity - which I am able to weigh due to the above constraints - Java's streams are not only simpler in design, but vastly more stable, and far more straightforward to glue together, and with highly stable implementations of ZIP stream wrappers and output-input pipes. A couple of additional stream wrappers for chunking into multipart upload segments and forwarding streams (introduced in JVM 9 when I'm on 8), and I was ready to go.

All that to say: don't create universal rules, though I agree that all of what you mention are good rules of thumb for certain. My given constraints work just fine with Java, here.


A minimal Java app can easily start up in 0.1 seconds, which might just as well be good enough, but if not, there is also GraalVM that can output a native binary.


I’ll just leave this with you. 0.1 difference may not seem like a lot until you start hitting 100M invocations.

https://mikhail.io/serverless/coldstarts/aws/languages/


Wouldn't you have a lot of warm starts with that many invocations?


At that point, why lambda?


Exactly what is enterprise? I think Java has a role to play in most professional settings, even if the company is quite small. It is not a sports car but I see it more as a truck or a cargo ship. Vehicles that underpin the world economy, but that only few think of as fun or exciting. Not pretty or nice to look at, but reliable work horses.


It is for sure the wagoneer when it comes to getting things done in the enterprise, if it was 2005.

It’s not a sports car, it’s not a dune buggy, it’s not even pretty in its wood paneling and drab paint scheme. But it got your family to Wisconsin for the holidays.

In 2023, we have better options.


Yeah, sure the thing that runs the whole of Apple, Alibaba infrastructure, many part of Google, most of that "cloud" infra is a wagoneer..


Such as...?


> No one really enjoys debugging your call stacks

Of all the things to criticize about Java, this IMO is not one! A Java call stack is a joy compared to just about anything but Python.


It's not so bad. I might be an old masochist with Stockholm syndrome, but every language has its own special flavor of barely tolerable bullshit.


There is some truth to this. Every language I used had severe drawbacks. At this point, it's choosing the lesser of the evils.


so, which lang would you use personally?

> you still have threadpools and os threads.

why this is a bad thing exactly?


Golang or Rust. If it’s a web service or microservice: Golang hands down. If it’s a desktop software or game engine, rust. If you just want to typescript your way to success, deno and vite. If you’re too introverted for Rust, Zig.

Java, whether it be spring, micronauts, jee, whatever, is wasting CPU and Memory in the cloud costing you and/or your enterprise money.


> Golang

golang is probably a good contender for business logic code where Java is widely used, but I feel ecosystem (libs, integrations) is not comparable to Java, so you take some risks while choosing golang.


And it is much more verbose, it is not even comparable in observability and on real world big applications (especially enterprise) you can't get away with value types and slowing down the threads to let the GC keep up with them -- Java definitely shines in these kind of conditions (GC-wise the only competition Java has is different Java GCs, really).


> you can't get away with value types and slowing down the threads to let the GC keep up with them

I am not Go expert, but to me this is Go's big advantage: you can chose you want to have object GC controlled or be on stack and copied everywhere. GC controlled objects add lots of overhead, because malloc is expensive, and require lots of memory per object to track state and synchronize between thread, and that's why JVM tries to adapt something similar: https://openjdk.org/jeps/8277163


Sure, value types are a good thing, but they are no panacea in and of itself.

Also, any non-toy GC won't be using malloc, e.g. in Java's case allocating objects is barely more expensive than allocating them on the stack: they use a so-called thread-local allocation buffer, which can be used to allocate new objects in, without expensive synchronization, and the GC can quickly scan it, moving still alive objects out of it, and clearing the buffer.


> they use a so-called thread-local allocation buffer, which can be used to allocate new objects in, without expensive synchronization, and the GC can quickly scan it, moving still alive objects out of it

yeah, all these logics still have significant overhead, especially memory wise, it is hard to reason when JVM decides to kick that or another optimization or not kick anything at all.

With value objects you have full control and bare-metal-native performance without compromises.


> you have full control and bare-metal-native performance without compromises.

Not even you believe that, right? Especially with regards to Go.. Go is closer to JS than to Rust/C++.


as soon as you copy your struct by value around, it is easy task for escape analysis to decide to put struct on stack.

also, golang has arena API now, and it also makes heap allocations super cheap if you manage to integrate it into app life cycle.


Golang doesn't have copy elison unlike C++ so in principle you need to leverage the heap escape anyways.

Golang's arenas were an experiment that has already been ditched by the maintainer as un-feasible.


> Golang doesn't have copy elison unlike C++ so in principle you need to leverage the heap escape anyways.

I mean you can pass structs by ref or value around, which makes direct impact will they be on stack or heap.

> Golang's arenas were an experiment that has already been ditched by the maintainer as un-feasible.

Thank you, good to know, looks like big pros in favor of rust as lang for my next project.


I have not used Go generics, but it's viability as a business logic language would depend on that.


Wait, what?

Generic programming is a perfectly valid style, but are you saying it's essential to implement business logic?

Because, like, the last 3 languages that got adopted as the default business logic language didn't have them (Java pre 1.5, C, and Cobol).


It is not essential, but without it, it is much more cumbersome. I programmed it with Java so I know it can be done but I would not choose a language without it if I could.


It is absolutely necessary nowadays, yes. Our applications are often more complex than what people did in Java 1.5, Cobol, or C, and definitely need to be developed at a faster pace.


and during last 20 years consensus has converged to the view point that generics are essential.


Huh. Guess I'm heterodox, then.

I mean, don't get me wrong. It's quite useful once in a while, especially when working with containers, but I don't think of it as essential.

But I'm fine with it as long as people don't overdo it.


Rolling your own containers really really sucks. Sucks enough to the point that it is essential, in my books.


Maybe I should be more clear.

This thread was in the context of go generics, which generally means go 1.18, when user defined generics were added to the language. Go always had support for a minimal number of containers, I've never rolled my own when working in the language.

Go had special compiler support for slices, maps, and channels before then, and they got used as the default containers for everything.

Using those was annoying when you needed to do the same thing to, say, a slice of strings and a slice of ints, but if you aren't using a generic heavy style, it actually comes up surprisingly rarely. And, if I'm being honest, I'd consider most of the uses of generics I've seen the average enterprise dev use to be mistakes.


they have generics now, but there are other conceptual shifts:

- no inheritance

- error codes with explicit handling everywhere


no inheritance but there are interfaces to be able to provide alternate implementations. Similar to traits in Rust. If it quacks like a duck, has feathers like a duck, swims like a duck…

Go just assumes if you satisfy the interface, you’re good.


maybe, it is just some effort for someone who coded in classic OO for last 20 years to wrap head around this new concept fast and judge if it will satisfy all/most use cases.


Yes those are also drawbacks


golang is a simplistic language which lacks the modeling capability of Java, so it won't do well in large business code.


TIL that projects like terraform are - apparently - not "large business code".


You can write basically anything in any Turing complete language, doesn't mean it's a good idea.


This ... is how you make engineering choices?


At work, I have a different context. What is the common denominator of what people know. Personally, the “write once, run everywhere” notion of Java has been replicated with much more productive languages.


Sounds like you're talking about a pop band, not a tool.


Call me back once golang is going to have anything close to visualvm and remote profiling capabilities.

Is golang still using mark and sweep gc?


I agree, Java is wordy, ritualistic, and prone to overcomplication. Build/run/write loops are slow and painful, and frameworks and toolkits try to do so much that they inevitably get in the way.


Not by that much... the problem is Java's build systems. Theyre what complicate and slow down java development 90% of the time.


The problem with Java is not Java. It is Oracle and no amount of new features is going to fix that.


Can this fkin FUD finally die? Java is pretty much the safest bet on a longevity, stability, risk aversion spectrum. Also, Oracle just has a particularly bad PR, but it is not at all worse than any other similarly sized company.


Oracle has definjtely done bad things; mismanagement of java is not one of them. They got a pile of crap and turned it into something competent.


I am wondering if Oracle is really playing significant role in Java evolution, they have some open process with many other companies contributing.


They are responsible for like 95+% of all OpenJDK commits.


But, you know, Java too.


The biggest problem with Java is... the walled garden, the snob society, the elitist, exclusive culture.

Go isn't like that, or at least wasn't when G+ was still around.

Nowadays I don't get in touch much with people, but when I create issues Go people are usually not as unfriendly as Java/JVM people.


> The biggest problem with Java is... the walled garden, the snob society, the elitist, exclusive culture.

No idea how you have managed to come to such a conclusion, or what kind of experienced you might have had. I have been part of some of the core JSR/groups, as rather independent/unaffiliated (not oracle/google/hp/ibm/twitter) - there was no gate keeping, elitism or anything alike.

I'd consider mailing lists extremely professional, respectful, even nice.


Agreed, I have never seen a pattern of elitist behavior unique to the Java ecosystem.

There are always people like that when groups get big enough.

The thing I love about the JVM ecosystem is the engineering culture. The quality of the platform and most libraries is very high, and well designed.

I don't love the Java language, but it can get the job done.


Rudeness scales mostly with the size of the community. If you go to something like Nim you will immediately find them way more nice than Go, just from this.


Java? Elitist...?

Maybe it's true for Haskell or Rust. But Java?


Imo problem with Java is... JVM because it's quite a resource intensive application itself, memory usage is orders of time magnitude worse than most of the language I've used.


Interesting. A Spring Boot webapp (with the runtime dependency injection framework, etc. etc.) serving some static content and exposing some REST endpoints works fine with 32 MB RAM. Is it really orders of magnitude more than other languages, e.g. will a Go-based webapp consume less than 300 kBytes of RAM?


I do believe that number will go down in the coming releases; things like valhalla will allow us to pack data representations much more efficiently after all. the only things that really benefit from object identity are behaviors, not data.


That's almost always a consequence of poor engineering. You need to consider the runtime when developing any program. With Java, that runtime is more than just your CPU and OS, it's the JVM too.


Forgot that our computers were so resource constrained these days...




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: