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 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.
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.
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.
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.
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.
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.
I am not touching Go, other than on the projects I have some customer or higher up telling me to do so.
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.
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.
How does that vary from Java?
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.
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.
edit: Sorry, it's actually:
try (var executor =
Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(...)
)
instead of `go` Thread.ofVirtual().start(() -> System.out.println("Hello"))Doesn’t sound like a fair comparison thou the Java version is missing things
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.
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?
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).
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.
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.
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.
Honestly, its way too early. Virtual Threads will need a a "killer app" (in this case a killer framework) to pull in devs.
Also, was that a clean build?
For me bigger feature is pattern matching for switch and records.
Go, Rust, Ruby. These three cover all cases.
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.
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?
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 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)
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".
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 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.
> 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 neededTo 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 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.
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.
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.
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.
I'm really excited about https://jspecify.dev/, which is an effort by Google, Meta, Microsoft, etc to standardize annotations, starting with @Nullable.
I know Kotlin is basically supposed to be that, it has a lot of other stuff though, and I haven't used it much.
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).
More intuitively and succintly: product type is equivalent to a cartesian product of sets
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
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.
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?
> 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?
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.
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.
Uint32 a,b,c;
c = a.plus(b);
in what you describe, once project valhalla is complete?Signed, a dev who would never willingly choose Java over Kotlin for anything ever again.
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.
[0]: https://arrow-kt.io
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.
But, yes, namespaces are still mapped to paths, it's not like they rewrote the JVM to use blockchain or something
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
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.
C# keeps being the language people are looking for but don't know about.
Hard to overstate the mindshare that it lost for that
With that said, it is a cool language and platform, that is indeed underhyped.
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.
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).
In my limited 6 years of professional experience as a software engineer, I have never met a single person who writes C# or .NET.
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.
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?
We all know C# exists
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.
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
That seems absurd to me and I have a hard time understanding it, honestly.
Has the java culture move so far the last decade ?
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.
https://en.wikipedia.org/wiki/Design_Patterns
Don't take undergraduate OOP courses seriously.
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.
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.
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?
> 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?"
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.
Those are two different ideas.
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.
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.
> 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.
What else do you want from the ecosystem?
I remember programming Java in Eclipse. And it was powerful for the time, but everytime I read bloated, I automatically think Eclipse since then..
I’m sorry, I’m calling BS here. You can still leak memory in Java.
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. :)
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.
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.
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.
Like I academically understand disliking Java but this just makes no sense.
Because there is plenty of bloated and dirty stuff in C++ and Python too.
* 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.
Nd it is very compatible with Java itself, interop is dead simple
Dead.
In many ways Java’s boringness is a feature that keeps it easily maintainable, at a low - easily recruitable - skill level.
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.
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.
Maybe it's true for Haskell or Rust. But Java?