Why Java Is Dying
medium.com
medium.com
The cruft in Java, and hacks like Lombok to reduce it exist in every statically typed language I've used. And if you've worked on a big enough project you'll start to see why dynamic languages like JS and Python are bad choices for enterprise apps.
It's telling that every major dynamic language now has optional typings. Once you get to a certain size the ceremony around access modifier and statically typed objects makes sense.
And the author falls into the common idiom of "the JVM is huge it uses too much ram". Then advocates for dynamically typed languages with even bigger run times that use multiple times more ram. Dynamic languages like Python and JS, as a generality, use 3-10X as much ram as VM languages like Java and C#. They're also equally slower. And the runtime argument makes zero sense. V8 is massive. CPython is massive. Any popular language over time develops a huge runtime.
The author also complains about Spring. Spring isn't Java. There's a ton of Java web frameworks that aren't Spring. No matter how much you like Java you can always find common ground hating on Spring.
Overall the article is a common set of complaints from developers that never used Java or only did a few Spring tutorials, got frustrated at the complexity of the language, and moved on to something else.
Java is one of the fastest popular languages and uses way less ram than many alternatives. And the JVM is a great runtime, not a bloated one. Look at how massive V8 is and the JS standard library is still complete trash compared to Java's. The JVM is possibly the best runtime out there, and a big part of why Java is so popular.
The JVM has great and fast FFI, profiling, metrics, multiple world class GC's, it's rock stable, it optimizes code to within spitting distance of C, it handled projects with hundreds of megs of code with no problem.
The one that gets me is the microservices. I've never really understood it. I can build a Java project with a bunch of small, composable modules and things like refactoring across the entire codebase are trivial. There's no rule saying you can't put your (ex) auth API in a separate module and bundle it independently at build time, right?
Or, if you want to use the same module in multiple projects, formalize the versioning and bundle it as a library.
I think the thing that drives microservices adoption is that it's easy to start out without any planning or API design. You just yeet some code out, publish the REST "API" via Swagger, and call it a "service". There's no versioning, no clear rules, etc.. All of that becomes tomorrow's problem.
Then, since you don't have proper API versioning or anything, you start to end up with things like mono-repos to ensure your "API" changes aren't going to break anyone relying on your "service".
Seriously. Microservices in mono-repos. It's like a bad joke.
In Java (and similar) "enterprise" languages you have static typing, access modifiers, a quality module system. It's trivial to split your app into multiple independent units.
And the JVM + CLR are designed for giant monstrosities. They use a compact bytecode format, zip their dependencies, and do crazy things like runtime code unloading and in-memory string deduplication.
We have Java apps with millions of lines of cruft that run fine on reasonable machines. Totally anecdotal but our worst disasters are dynamically typed stuff like node where the only way to know what the objects being passed around look like is to see them in a debugger. And with JS the "module system" always devolves into webpack concatenating everything into one giant file. It's pretty gross
First of all, Spring is not the only framework out there (even tho I really dislike it). If the title were "What I hate about Spring", it'd be fine, but hating on an entire language because of one framework just misses the point.
> Java’s focus still appears to be on silly rules that dictate what class names should be, what packages they should be in, and if variables should be private or protected. Seriously, who cares?
This is the one thing that I like about Java. I can easily guess the names of classes/methods. Private variables are private so that they don't leak into the public API (ie. encapsulation). The "seriously, who cares?" at the end just discredits the entire article for me.
I've come to appreciate some aspects, but it's not obviously good to me, like a lot of stuff it's just various tradeoffs.
I like Java and the surrounding ecosystem/community because it's boring.
It doesn't for me. I can't keep track of all the times I've been frustrated by a "final" class or a private variable somewhere that, were it public, I could have trivially solved my urgent business problem in time, with full awareness of the tech debt being incurred.
But I can't remember any time in the tens of thousands of lines of Python that I've written, where I had regretted consuming a variable somewhere.
So at this point, based on my experience, it all just sounds like paranoia to me that doesn't actually add value in practice. I think many of these "best practices" in Java, especially those coming from "Effective Java", are over-paranoid for most developers, and just create more problems than they solve. Being paranoid about API contracts makes sense if you are writing a widely-consumed library like those that Josh Bloch worked on, where the cost of changing consumers is prohibitively high. But when you are working on a class that has a handful of consumers, it really doesn't matter--just go update the consumers if you really need to (which you probably don't).
And if you're using something like IDEA it's even better. You can take a guess at the method name, look at the auto-complete options, pick one, and hit CTRL-Q to read the Javadoc for that method.
Joke aside, while I don't like typical Java frameworks' complexity and haven't done any Java in seven years for that reason, calling java dead because “everybody is using microservices” and “why bother with performance when you can just run more workers” is a bit ridiculous.
First of all, in practice “everybody uses PHP” is still way closer to the reality. The microservices hype train is real, but it's far from ubiquitous in real life and the fashion will probably be dead long before Java (I'm not saying microservices are bad per se, but it's way overused, by companies who have zero scalability issues and adds a lot of engineering complexity).
Then there's cost: deploying 3 times more machines because you use Python on a high traffic service is a good way to give some margin to the cost-killers in the next LBO.
So far I’ve yet to see a micro services architecture that I don’t think is massively over-engineered.
Also you can write your over engineered micro services perfectly fine in Java. There are even very nice tools for that specially designed for Java.
Both the monolith and the microservices are written in Java with Spring Boot, but I agree with the author that Spring especially leads to runtime code that is extremely difficult for developers to really understand. I am curious whether writing future microservices in a language like Go might be more maintainable in the long run.
Go evangelises simplicity but when someone has to use it in a bigger project the lang is very cumbersome. You don't have any functional programming features like filter, map, find to manipulate collections. The not null (nil) safety or the magic with interfaces{} does it make non safe especially compared to modern languages like Kotlin,Swift which have the notion of nullability build in the language itself. Dependency Injection support is very limited. I think Go is hyped because of the excellent single binary feature which you have nowadays via GraalVM also for Java, Kotlin as I described above.
5 years from now you will go from a massive, over-engineered micro service architecture. To a well designed modular monolith that is much cleaner and easier to maintain, scale, etc.
The style you rewrote in didn't solve your problem, the act of rewriting did.
Microservice advocates are so naive it's painful. It is only simpler if you completely ignore the downsides of microservices. Taking a "monolith" application and turning it into a distributed application rarely makes it simpler. Now you have to deal with network failures, serialising and deserialising data for what was previously just a method call, API versioning all over the place, kubernetes/terraform/etc configs from hell, concurrency and race conditions still persist but now they're spread across services instead of between threads. You want data consistency and transactions? Forget about it!
For ex., filling a shopping cart should be its own thing. Searching for stuff to fill that cart can be another service that is just a dumb catalog. No data consistency problems here.
Version hell? You can imagine the horrors. But technical debt on an architectural level is something you need to consider any architecture you choose to create your application in.
Point of micro services is have your components loosely coupled and independent from one another. If catalog fails, it shouldn't bring down the whole application with it. In our simple example, catalog failure means whole app becoming inert, but in bigger applications you _may_ find benefits of this.
Also worth mentioning - load can vary between components. The cart will see less use than the search service, while the catalog will be most loaded. You can make deployment decisions to optimally allocate resources for your application.
Undoubtedly, 10 to 15 years ago he'd write and article about how Java is dying because Struts.
20 years ago it would be an article about how it's dying because there's nothing to compete with the new kids on the block in the web space.
Java isn't going anywhere. It will probably slowly lose its significance over a few years, but it will be a decade or so before it will actually start dying (if at all).
> My beef, in fact, is more with frameworks like Spring than Java itself.
True. The whole article seems to be about Spring.
How about I write an article about how the Python is crap because I can't do banking with the Zope libraries? It's ridiculous.
I would hope these kind of article would die.
If you don't like Annotations then don't use Annotations!
Then you won't need to write inflammatory headlines ...
This kind of thing irks me when I read it. I don't understand why people think of microservices as an architecture pattern. To me they seem more like an implementation detail.
When it comes to concurrency and multi-threading, microservices mean you have different problems to worry about, not that they go away. I try to get people on my projects to imagine microservices as network function calls. If you diagram out how your data flows, how your concurrency works, you tend to see that a microservice is roughly a thread in a monolith. You gain not worrying much about mutexes or shared memory, but you lose on performance. Languages like Rust let you get close to having your cake and eating it too.
It is also a useful exercise for teams to consider "how big does my service need to get before microservices on Python scale better than a 64-core monolith in C/C++/Java/Rust?" Another good question, "if a monolith is stateless, does it scale as well as microservices?"
Implementation details and architectural patterns are orthogonal.
Implementation detail means something that is-not/should-not be exposed at higher levels (e.g. API consumers and/or the end user). This includes architecture, but also all kinds of other things (e.g. choice of storage backend).
Microservices is very much an architecture pattern (affecting the whole architecture/design/code-base of the app in the most fundamental way), and is/can-be an implementation detail, as far as API/UI users are concerned.
If you use constructor injection as it is recommended, just add a breakpoint in the constructor and start the app in the debugger.
@Component
public class Foo {
public Foo(Bar bar) {
// breakpoint here
}
}
Edit:In most cases it should be obvious what is getting injected, e.g. in the example Bar is another component/bean that was manually created. If you have two beans of the same type, use a qualifier to specify which one you want.
If you are having issues with unexpected things being injected, I'd recommend setting up a few tests that establish that your app is properly wired up.
Spring sucks indeed. But Java Play Framework is great
You haven't lived until you've been serious with Java at some point imHo.
Still getting up to speed on Go, but it seems a more straightforward replacement for Java in a microservice context. More performant than Python, trivial to deploy, and provides a good amount of static type checking.
I had a chance to reflec on what I like about Go, and what I miss from Java. To boil it all down:
* I prefer the Java language over Go, for a bunch of reasons, although neither is perfect.
* I very much prefer using binaries over bytecode objects, so Go is much better here.
* The JVM is a very large runtime to associate with your code, and the startup time is still much longer than for Go (or it was a year ago). This makes Java CLI tools slow, and a great CLI can help make a great product.
* I prefer Go’s approach to the standard library; easy to use production ready “for dummies” implementations, instead of abstract “reference” implementations.
* I specifically don’t like Go’s use of capitalisation to mark fields and variables as public, since I find that collisions between type and variable names are really common.
* Go’s “idiomatic” practices are much saner than Java. I’d already stopped using getters and setters in Java, along with dropping several other abstract “best practices” (factory factories!!?? “Fluent” APIs??), and Go really embraces a light approach here. So I guess I don’t really like idiomatic Java. I got the feeling I wasn’t alone, tho.
* In general I like how Go feels like it’s a Unix tool, where Java always felt like they were trying to rewrite the world. This approach always felt odd to me, considering where Java came from.
* I have really come to hate the “everything’s a framework” approach in Java and abandoned it a couple of years ago. Especially, Java EE, Wildfly, Spring, JPA etc. they all add enormous weight and complexity without realising any material benefit. It’s not that it has to be this way, and I think things were improving, but so many Java libraries were just massively complex and abstract, for no good reason.
So for my use case, Go is clearly the right choice, but after 12 months working in Go there is plenty that I miss about the Java language. I had really high hopes for the Java AOT stuff but it seemed to stall (maybe I missed something).
> In general I like how Go feels like it’s a Unix tool, where Java always felt like they were trying to rewrite the world.
Go is pretty much on par with Java when it comes to rewriting the world albeit not in the same way: they have their own assembly, their own linker, and even have their own libc using raw syscall (even doing so on windows and Mac for a while, which caused issues because there is no syscall stability there). And since it has a poor FFI, many Go developers end up rewriting their own libs in Go for pretty much everything when they need one lib.
But my point was more that, relative to Java, the performance and CLI tools feel very Unix like, they remind me of my C programming days. I mean Javac/mvn/gradle are all so slow relative to Go; Java command line tools don’t really seem to care about developer ergonomics. It’s hard to imagine chaining a bunch of Java CLI tools together with pipes!
This is more because of the long startup times and cost of the JVM, but that too is part of the problem.
Project Leyden [1] aims to standardise AOT for the Java Platform. It is planned to start sometime in the new year. We will most likely end up with two implementations of it. One is GraalVM's native image which will most likely need to make some changes in order to follow the specification. And another implementation will likely be one that is based on HotSpot's C2 compiler [2][3].
[1] https://www.infoq.com/news/2020/05/java-leyden/
[2] https://github.com/openjdk/jdk/pull/960#issuecomment-7220230...
[3] https://cl4es.github.io/2020/12/06/Towards-OpenJDK-17.html