I agree so much with this article. Java EE practices bloat programs, waste memory, and make debugging harder. Let alone all the language constructs.
> Java allows null objects… that ship has sailed
Not necessarily, with project Valhalla, it should be possible to have generic Value Types in the language that would allow things like Optional to be defined on the stack where it would be non-null and then all it’s methods for checking if the Object it references would make sense, and actually work. This would be in contrast to today where the Optional type isn’t as useful as it seems since it can also be null.
I've seen many other developers trash java because of the same reasons, often citing Go as a great alternative.
The problem is, for every org I've seen, saving memory is not the goal of the program. "Making debugging harder" is subjective, and most people I've heard use this argument are taking about "magic auto configuration". Maybe this isn't intuitive at first, but the knowledge comes quickly, and from my experience the java app with unit and integration tests is far easier to debug than the Go app with less testing and less readable syntax.
The memory argument is also way less applicable when using the author's framework, because your app is built into a native image.
I know you didn't mention Go, but that's where I always see this argument headed, and it always makes me wonder whether the writers have ever had to maintain or take over an app written in Go, or if they can happily write-and-forget.
I don’t think we even need to discuss Go though. Just compare the ease of using vanilla Java to the complexity and magic that have become popular (mostly to work around limitations in the language). Being able to follow code easily is an important aspect of debugging a program, Spring and etc, obfuscate implementations. Are they still worth it to use? At this point for many orgs unwinding that would take years.
That’s at least what I got out of the article, Java doesn’t necessarily need magic frameworks to have a good development environment, that resonates with my own experience.
Modern spring boot apps I've seen are actually really easy to debug. More often than not there isn't even an interface used, the dependency injection is just for having easily mockable unit tests. In my opinion it doesn't feel obfuscated or hard to debug, especially because the tests can be easy to read and write.
In saying all this I normally use js or python for personal projects, it's just that I still see JVM projects specifically using these frameworks (spring, quarkus) working fantastically for long lived, enterprise apps
I don’t know, I personally prefer a standard solution for common business requirements like validation, conversions that will be the same regardless of which project I choose over some custom-made ones.
Another way to think of Spring’s magic is just declarative programming to a degree. You write what to validate based on rules, not the how.
> The problem is, for every org I've seen, saving memory is not the goal of the program.
Thing is there are huge number of people, group, companies, projects and so on looking to save memory all the time. It is most of the time done by library, framework developers so generally app devs do not notice.
Oracle teams are spending ton of effort on GC efficiency, reducing object header size, or value types. If saving memory is not an issue they would not waste huge resources like this.
I have maintained Go apps and it turned out to be so simple because it is grand total of one Go file with 1.5K LOC of code. This was exactly opposite of Spring Boot apps which are used a lot here. They are perennially stuck due to some or other library upgrade required for security fixes.
If I'm in a Java shop, then the main thing(s) people are advocating for are, in order of volume.
- Kotlin
- Scala
- Clojure
Go may well compete on green fields, but if the culture is Java, they only have n lateral moves, and have a legacy that's generating value for the org. (on JVM)
Spring and (and Quarkus apparently which I haven’t used) brought along a lot of the Java EE concepts. It lives on if not in name, in spirit. Thus the cultural problem this article discusses.
Well technically they are built on JEE APIs, but JEE is not a 10 page spec, so combining all of JEE into a pool of "horrible designs" would be somewhat naive.
This take is so exhausting. It really is, "Tell me that you know little about Java beyond forum comments that you've read, without telling me that you know little about Java beyond forum comments that you've read".
The most important Java EE specifications (now "Jakarta EE", as "Java EE" hasn't existed for years), are: CDI, EJB, JPA, JTA, JMS, JAX-WS, JAX-RS, JSTL, JSP, and JavaMail.
Of that list, a typical Spring Boot application in the wild miiiiiiight use JPA. And that is totally optional, with a handful of newer ORM frameworks seriously eating into its market share. Even JPA itself didn't come out of the "Java EE" world. Hibernate won the ORM wars back in the day, and so the Hibernate folks were allowed to write the JPA spec, making it near-identical to the existing Hibernate API.
Spring has extensions that allow it to interface with nearly anything in the Java ecosystem (and its DI container makes it easy to write an integration to anything new). However, while it CAN interact with all of those Java EE API's, it doesn't NOT "depend" on them!
The story of Spring's relationship to Java EE is the classic "embrace, extend, extinguish". Reddit and HN are able to grasp that pattern just fine when they are slamming Microsoft. However, they can't grasp get it in this context. I honestly believe that the vast majority of people's Java knowledge on Reddit and HN comes from ignorant comments that have been in a self-reinforcing recursive loop for years.
Most Spring deployments I have seen in the wild aren't using Spring Boot as such, rather Spring MVC, and yes they do depend on them on most enterprise shops.
But what do I know, I only took part in the Nokia NetAct from CORBA Perl/C++ into Java EE migration, have done my share of Java projects since then, and nowadays all our projects tend to have a mix of Java and .NET, so guess I hardly see any Java code. /s
> Of that list, a typical Spring Boot application in the wild miiiiiiight use JPA. And that is totally optional, with a handful of newer ORM frameworks seriously eating into its market share
Could you cite some?
Specifically any that encourages immutable objects (like records in entities).
For one, Spring offers "Spring Data JDBC", where the Spring Data repositories that people like so much can be implemented with raw JDBC rather than using a full ORM.
Beyond that, there is JDBI (another thin wrapper around raw SQL), jOOQ, and MyBatis. And then a number of other options like Apache Cayenne that are interesting but never really caught on.
The best description I have seen is "type safe SQL" and it's a default choice for me too. One of the rare occasions where code generation in a Java library doesn't feel like magic and doesn't require littering your code with annotations.
Yes I heard that, but it has other issues - code generation :(
And AFAIR it needs a license for usage with Oracle, and getting approvals for something like that in a corp is PITA.
I believe there's a pretty common approach implied here that assumes that SQL DDL is what you start from. It's nicely aligned with how RDBMS schema changes are usually applied too (i.e. a DDL script to make changes and possibly another to roll them back):
* define you DB schema in a SQL script under version control
* apply the script to your local DB instance, re-generate JOOQ classes (under version control too)
* update the service logic
* commit all the changes
* in prod, apply the script and then re-deploy the code
Hop on LinkedIn and search for job openings mentioning spring-data-r2dbc, or spring + r2dbc.
Edit: r2dbc being reactive is founded firmly in the idea that you won't be getting proxy objects. I'm more of a vert.x fan myself so I haven't used it personally but I see CVs come by my desk with users of it.
I've never understood why this isn't a more common approach.
JDBC is an old standard, but it's also perfectly functional. The addition of try-with-resources has made it fairly terse once you know your way around it.
And half the time with an ORM, you are trying to figure out what SQL is ultimately being generated and tweaking to get what you want. So, cut that middle layer out and just write what you want.
If I have to use something like an ORM, then I push for JDBI or JOOQ. I've not ran into too much pushback using raw jdbc at my current employer though.
Forget the interface vs data structure (for which they provide little support), the final keyword is the horrific standout. Conspiring with the compiler to remove programmer agency, in the misguided effort to protect programmers from themselves, is ridiculous.
Reading these comments, it's strange to see that the "Java" paradigm/context issue still exists after all these years.
I have never had significant problems with Java-the-language. But, like with many other programming languages out there, you can't really dissociate the language from the environment and everything that goes with it. The paradigms, VMs, IDEs, patterns and practices etc etc.
And for the record it's not just Java (it just seems to be more pronounced in the Java-development ecosystem). For every person who waxes lyrical about the joy of working with C# (for example), there are 3 more that will tell you anecdotes about "assembly hell". Or how Visual Studio is slow and a massive memory hog. Or that there is no proper Forms support on XYZ-os.
Those are all fair points, but more often than not, they have little-to-nothing to do with current discussion. IMO, I think it's usually far more productive to try to understand the discussion in context than to veer off into irrelevant semantic issues or tangential anecdotes.
> Reading these comments, it's strange to see that the "Java" paradigm/context issue still exists after all these years.
> I have never had significant problems with Java-the-language. But, like with many other programming languages out there, you can't really dissociate the language from the environment and everything that goes with it. The paradigms, VMs, IDEs, patterns and practices etc etc.
This is something I've been saying for years.
Java, as a language, is great. But Java paradigms are fucking awful, and I don't understand why people use them. However, I'll confess that I'm naive, as I've never had to build anything larger than about 1,000 lines.
Like...one of the patterns that I absolutely despise is the "Handler". So often, code is peppered with "MyHandler handler = new MyHandler(); handler.invoke(...);". If you have an object that consists of only a constructor and a single function, and is merely instantiated, called once, and then thrown away, why is this its own object?
I see Java developers complain about stack traces that are 50+ function calls deep with half the functions in the stack named ".invoke()" or ".run()", and all I can think is how those wounds are self-inflicted.
There have been at least two mostly disconnected "Java"s for the last 15+ years. The job descriptions used to be very clear about. There were "Core Java" positions and "Enterprise Java" ones. Very different communities, different libraries (remember the revelation that the LMAX Disruptor was for example?) etc. The developers spoilt by the experience with Scala/Kotlin probably count as a third group now.
You choose weird examples. I don’t know how VS can be considered slow when it only has one real competitor - JB Rider - and that competitor offers C# support. Also, I’d there a language that competes with C# that doesn’t also have some form of “assembly hell”?
I really don't like Quarkus approach. It's full of magic. It's everything I'd love to get rid of. I'm using Spring which is kind of full of magic but I spent 15 years learning to deal with this magic, so it's bearable to me. But it does not mean that I like it.
I liked Helidon SE approach (not MP, MP is the same EE crap). It's 100% code, no magic. What I didn't like is reactive, I don't need reactive, so I don't use Helidon SE for my projects, I still endure Spring. But if someone enjoys reactive programming and wants zero-magic approach, I can recommend it.
Helidon Nima might be a saviour to me. I didn't research it yet.
I can't stand any magic, it always inevitably feels like a massive liability to me and always bites me in the ass sooner or later.
I use http://sparkjava.com in my projects. It's as tiny as web frameworks get, it only handles routing and request/response, and you get to do everything else manually. Only thing I had to hack into it was response streaming. The one most internally convoluted and enterprise-y dependency I use is, surprisingly, the official MySQL driver. GraalVM complains about it as well.
Thing is, it's so simple and straightforward, you don't have to google anything. Reading the sources and poking things with a debugger has been enough for me so far.
The problem with SparkJava is that it is not maintained anymore :( according to GitHub, meaningful work on it last happened in 2020. It was indeed a great tool, but now I’d choose Javalin since it is maintained, or even better, Ktor if I’m using Kotlin.
I agree with you about both magic and reactivity (meaning, IIUC, asynchronous rather than blocking APIs). Helidon Nima looks interesting.
But what I'd really like in Java is a non-magic, non-reactive server-side web framework that's designed to be used for full-stack, server-side web applications, including things like strongly-typed HTML templates, form validation and rendering, cookie-based authentication, and CSRF protection. Something we can use to develop a complete server-side web application, the kind that everyone used to write before the division into back-end API and front-end SPA became popular. Helidon Nima might be a good starting point for such a framework. Another starting point worth looking at is Javalin [1].
This is essentially the Java Servlet specification with JSPs (or if you don't like those, just bolt on Thymeleaf or one of the older templating languages).
Nearly all of these newer frameworks like Spring, Helidon, Quarkus, etc. just reuse the internal Sevrlet spec themselves. Otherwise, they're reinventing a TON of code that would be insane to do in order to deal with the general HTTP request / response cycle.
I'm not sure the point of Quarkus. Its an easy way to get started with GraalVM development, but pre-compiled Java isn't any use for dev work. The autoreload classes is easy in most dev environments already too.
It seems to me that the point of Quarkus is to allow old EE applications to be ported to Graal Native. For example they did that with KeyCloak, it was Java EE application running on top of JBoss Application Server, now it uses Quarkus.
I like SmallRye Config and I feel it's designed this way because of the problems it tries to solve. The following is an excerpt from the MicroProfile Config standard which SmallRye Config is an implementation of:
> The majority of applications need to be configured based on a running environment. It must be possible to modify configuration data from outside an application so that the application itself does not need to be repackaged.
> The configuration data can come from different locations and in different formats (e.g. system properties, system environment variables, .properties, .xml, datasource). We call these config locations ConfigSources. If the same property is defined in multiple ConfigSources, we apply a policy to specify which one of the values will effectively be used.
> Under some circumstances, some data sources may change dynamically. The changed values should be fed into the client without the need for restarting the application. This requirement is particularly important for microservices running in a cloud environment. The MicroProfile Config approach allows to pick up configured values immediately after they got changed.
One reason we don't know whether SmallRye Config is reading from a file or querying a database at development time is because that choice has been deferred to the system administrator at deployment time.
Records are immutable. If we want to be able to reconfigure applications at runtime it might make sense to use a mutable data structure.
What are you suggesting? That one should read the documentation? And have a level of understanding why things are the way they are? Blasphemy! Zero ignorant screeds would be written! Productivity would increase 10 fold!
I agree. Java has improved as a language quite a bit in recent years. And I don't even mean that in a "Java has followed the cargo cult of what's fashionable in programming languages"-sense. I mean that things like Records to define value types have "always" been desired in Java- at least in the 10 years that I've worked with people on Java code. If Java didn't automatically make everything equatable/hashable, it would be a different story.
Anyway, Java still has baggage that I doubt will go away any time soon. My biggest two complaints with Java are that null exists outside of the type system and that they mix runtime type reflection and non-reified generics. The latter features being actually incompatible with each other makes working with Java libraries/frameworks a minefield.
But, aside from those issues, the OP is totally right that so much of Java's bad reputation comes purely from library design and culture. Even when I preferred OOP architectures, I hated the fact that so many Java libraries and frameworks required a bunch of annotations for everything, even when it really wasn't necessary. I get that Java code can be verbose, but if you don't like it, use something else- don't just throw away compile time safety and information for a janky runtime-checked "DSL" where a bunch of the features don't even actually work together (I'm mostly looking at you, JacksonXML, but also Spring, et al).
Everyone praises the Java ecosystem as some kind of selling point, but I find that while the ecosystem is indeed broad, the actual quality and productivity of many of the popular libraries and frameworks is fairly low. And by "quality", I don't necessarily mean number of open bug reports, I mostly mean that the APIs are really difficult to understand and use correctly. In some sense the devs are "off the hook" when my app doesn't work correctly because "this is documented behavior", but that makes me think I should just start writing documentation for my apps' bugs and seeing what my boss thinks... ;)
Not sure what you mean by null being "outside the type system"--it's just the bottom type, assignable to all references.
I loved Java circa 2002-2005 and was deeply interested in JVMs. However I wanted to develop the JVM itself, and it turns out that Java is bad systems language. So I really want a systems language because VMs are the itch I have.
What's weird to me are the choices that Java has made in its evolution. In Java 5, the VM and library powers-that-be basically killed reified generics, forcing type erasure. That was a huge language design mistake precipitated by not wanting to upset the apple cart of libraries and the .class format. But in versions 6 and 7 they updated the class format anyway, to add stackmaps and invokedynamic! I never understood why such a key language feature didn't get the requisite VM support it needs to be done right, but completely marginal features did get VM support. Poor choices IMHO.
Java is just a big ball of semi-typed mud at this point. There are no layers and hardly any separation between what features are in the VM and what features are in the class libraries that are wormholes into the VM.
It's outside of the type system for two-ish reasons. First, because of the point the sibling comment made: while you can assign the singleton value `null` to any reference type's binding (but not primitives, so it's not even the bottom value for all types), it's not actually a value of that type, bottom or not. E.g, I can assign `null` to a `String` binding, or pass it as a `String` parameter, but I can't actually get it to behave as a `String`, so any call to a `String` method results in a runtime error.
That leads to the second reason I say it's "outside the type system": you can't explicitly write down the `null` type in the language. The reason you get a runtime error when we pretend like the `null` value is a `String` is because it's not a `String`- rather, the binding actually has the type `String | null`, but the compiler allows (forces us) to pretend that the binding is a `String`. Since we can't describe nullability in the written Java language (express it in the type system's language), but it does exist at runtime, I say it's outside the type system.
I would say it explicitly isn't the bottom type since it doesn't satisfy the Liskov substitution principle for any actual type due to null pointer exceptions unless you unreasonably axiomatize null checks into every single property any type may have.
Basically, you need to think of Java references as the tagged union of Void + the true type. But since nobody wants to discriminate and pattern match on every reference, most java code tries to either follow a convention of non-nullability + Optional, additional type annotations with static checks beside the compiler, or "trust me, I've done this a million times".
Lack of reified generics used to bother me. Than I started passing parameterized interfaces next to instances of that type parameter to access polymorphic "static" methods of the type. This allows for up stack dependency injection into the body of the method where you would want the reified generic type without being forced to extend the parameters of the method for every use case: that is, you get a much more flexible design this way.
> it doesn't satisfy the Liskov substitution principle
I haven't seen that principle applied so stringently before. Under a strict interpretation it wouldn't be allowed to override methods with any implementation that didn't have observably equivalent behavior.
> I started passing parameterized interfaces next to instances of that type parameter
This is just manual reification, and it's only skin deep. You still can't do List<Foo>[] because Java's builtin array types can't be polymorphic or extended with fields to store classes representing the reification.
Is it just me or is understanding those examples virtually impossible if you're not intimately familiar with that particular framework? Then again... I guess that's the author's point.
Maybe it’s different flavors of magic. One is intuitive, one is inpenetrable without being an expert or early on in the project lifecycle. Also java projects tend to grow way larger.
I dunno, this sort of thing seems intuitive to me:
@POST
@Path("/widgets/{widgetId}")
public Widget getWidgetById(@PathParam("widgetId") Long widgetId) {
// ...
}
...where the sort of `method_missing` magic that Rails uses is _far_ less intuitive. Since when can I just call a method that doesn't exist, and expect the language to contrive what it thinks is a sensible definition on the fly?
> Since when can I just call a method that doesn't exist, and expect the language to contrive what it thinks is a sensible definition on the fly?
Since Smalltalk-76 introduced[1] doesNotUnderstand: ?
I understand you didn’t really ask a question, but then I didn’t really give an answer (though the article’s discussion is fairly interesting). I guess my point is that there’s a distinction between a facility one finds unintuitive and a facility that’s newfangled and (therefore) outrageous. Even when the first category probably includes a good number of outright stupid ones.
Java projects growing way larger are likely just survivorship bias — python apps of similar sizes would just crumble, while java’s static typing gives enough stability against that.
I wasn’t disputing that java can grow larger codebases for various reasons: static typing and so on. What I am saying is that these systems do become impenetrable after a while and that magic that once is very welcome becomes the death arrow of obfuscation because while well meaning and useful at first, combined with a bunch of design patters and framworks it becomes a hot mess nobody wants to maintain.
> Also java projects tend to grow way larger reply
It's the latter. Java is more likely to be used by a large companies. Django is more likely to be used by startups and SMB's (as well as by students and side projects).
Forums like HN and Reddit are disproportionately populated by students, and younger developers who are typically more interested in discussing tech for their personal projects than their day jobs.
If your mindset is oriented around tech that is optimized for one-man projects or tiny teams, then you are likely to reject tech that is optimized for large teams in large companies.
I think most senior or experienced Python developers tend to steer clear of Django.
It's just that you don't hear about it because it's mostly junior / less experienced developers writing about Django online.
edit: having said that, I wish we would be more skeptical of magic frameworks like Django in the Python community at least. Things like 'get_object_or_404'[1] should be burned to the ground.
The examples use CDI (dependency injection through annotations) and yes, these annotations become hardwired to the business objects... Java has some kind of traits now through interfaces so it's not much of a problem today.
Java encourages doing things like they were done 15 years ago because rewriting reams of low-value boilerplate with delicate architectural impacts is neither fun nor (at first sight) valuable.
Typical immature enterprise organizations tend to extend the system with low-risk new features instead of refactoring and remaking old stuff.
This is why I left Java. Everything "looks nice" on the surface with annotations, but you loose the flexibility of code and a lot of the available tooling. Plus you need to be a real expert to debug issues and solve problems; understand framework architecture and read reflection code. You can't just put a breakpoint in there and start debugging. It's optimizing for the wrong thing.
The bad news is; this culture is permeating somewhat into Rust now that more Java devs are moving over.
This whole attitude is IBMs fault. They tried to get into the Java market by applying their big iron terminal thinking to create Java EE.
Java under Suns management was a lot more like golang today; pragmatism, fewer language features, and focus on writing code.
Java EE was created by Sun themselves, it was reborn from a Objective-C framework for OpenSTEP called Distributed Objects Everywhere, better get your history straight.
Yes, unless you consider Wikipedia info possibly bogus.
"By the time DOE, now known as NEO, was released in 1995,[1] Sun had already moved on to Java as their next big thing. Java was now the GUI of choice for client-side applications, and Sun's OpenStep plans were quietly dropped (see Lighthouse Design). NEO was re-positioned as a Java system with the introduction of the "Joe" framework,[2] but it saw little use. Components of NEO and Joe were eventually subsumed into Enterprise JavaBeans.[3]"
Interesting. I'm not arguing, but I'm very interested in this history, because I was personally bit by Java EE.
The EJB wikipedia page says that IBM developed the first version
> The EJB specification was originally developed in 1997 by IBM and later adopted by Sun Microsystems
, and there's also some other references online about "San Francisco" being folded into EJB;
> The San Francisco Project (SFP) basically failed, with much of the technology being folded into other efforts, notably Enterprise Java Beans (EJB) in 1998.
Java EE might have been one of the most colossal time wastes foisted onto well meaning developers in all history.
I think that played played a huge part in the rejection of these heavy frameworks we saw in the early to mid 00's, and some of early Agile's (XP) resistance to complexity and speculative development. And also why Spring became a thing. Now we view Spring as the heavy and complex beast!
The article is talking about Quarkus, a framework that uses the same annotations but which generates Java code at build time. So there's no reflection and you can navigate with your IDE or debugger into the framework code to understand what it does.
The things you hate about working with Java, that you mention in the first paragraph, are the basic things that Java devs have to do, or actually any dev in any language.
In a sense this is about the fundamental difference between frameworks and libraries. So to a certain extent, you're right. But there's extra complexity in not knowing why some annotation was picked up. With any API call you know you're trying to register something and can debug into it. With an annotation project you're sometimes stuck at "nothing happens".
It makes me laugh how people mention that C# or Java has "magic", which is just the lack of understanding a feature of the language, but in the same breath says that Rust doesn't have this problem. I rather be able to debug a C# attribute than a Rust one.
> In Java, you can forget about doing it in the cleanest or the best way, because that is impossible. Whatever you do, however hard you try, the code will come out mediocre, verbose, redundant, and bloated ...
+1, I love this article. Do language developments since 2014 invalidate any of his characterizations?
I've read this quote after working one year on a java project, and it just nails the sentiment. Both from devs like me that wouldn't pick java where you'd expect this kind of sentiment, but also from veteran java devs who also treat "quality" as something simply unobtainable.
Can we maybe also talk about FP’s cultural problem: that lots of people with a functional programming background just don’t get object orientated programming and instead of trying to understand it, dismiss it as crap and enterprisey.
The very idea that FP programmers don’t understand OOP is absurd on its face. You point to an undergraduate CS program that is teaching FP before imperative and OOP paradigms; you find me an FP zealot who hasn’t written more OOP than FP code.
This is a nonsense “reverse racism” argument rehashed to be about programming paradigms, and I won’t stand for it.
When I was there, intro CS for the college of arts and sciences at UVA was taught in scheme. Lisp-y intro classes can be found at a large number of universities. MIT now teaches intro CS in python (multi-paradigm, not OOP) but famously taught it in Lisp for many many years.
Why stop there? Why not go the whole way and teach CS in Javascript? Python's tentacles of mediocrity know no bounds. It's as if the whole teaching profession is sleepwalking towards conformity.
A lot of places do teach intro CS in javascript because it solves the environment problem very nicely. Everybody has a web browser and so everybody has a javascript execution enviornment.
I don't see how this is so terrible. Few of the actually hard problems in either software engineering or computer science actually depend deeply on the programming language one uses. FP isn't somehow morally superior to other kinds of programming. Several of the engineers I respect most on this planet started programming Perl. They didn't die.
That is also OOP cultural problem: that lots of people with a object oreinted programming background just don’t get functional programming and instead of trying to understand it, dismiss it as crap and too pure, unpractical, over-engineered and too theoretic.
I am a C# fanboy semi-greybeard with a very extensive OCaml university background and FP advocate (in C#) and I am still struggling with monads every single time I have understood them for a second.
Functional approach is always under consideration and a default.
OOP is useful in certain contexts (e.g. GUI elements) or simulations, otherwise it's better to use OOP in a dependency-injection way to construct the process then let the relationships be static while performing computation processes with them. The full dynamic reconfigure objects willy-nilly in an area where that doesn't reflect the problem itself is doomed to be a mess. Especially with threads using mutexes or collections that do.
I agree with the general sentiment of the article.
It is clearly possible to write a straight forward Java code. See: Sparkjava, Javalin, or Vert.x. Hell, even Minecraft Java code is pretty easy to read.
Too bad Java culture is dominated by consultancy-like culture, which promotes as many obfuscation as possible.
The world will not end if you don’t use annotations and DI frameworks.
It would be an interesting observation in 2007, but fifteen years later with no industrial success of Haskell-inspired pure FP approach, I don't see why we should listen to "any FP developer" criticisms.
It's not that I have anything against FP - let's just admit that side-effect pure FP is a "futuretro" of today. A future that never was.
I do think FP in Java is a bit of a mistake. Record types has done a lot more for the language than streams and lambdas.
I initially liked the idea of adding functional paradigms to the language, when the dust has settled all it does is create a sort of nasty Cronenberg language with most the drawbacks of both paradigms and few of the benefits of either.
I absolutely disagree. Lambdas has in many ways revolutionized Java and put it back on its feet. Java 8 has seen faster adoption than any other major Java version because of this. Compare it with Java 11 or indeed with absolute non-adoption of Java 14.
Streams are not very well designed IMHO. That's why their usage remains limited. Much more useful and robust libraries are possible on top of Java lambdas support.
Since everyone is now on Java 11 (and many on Java 8 still), we will only really see the records used widely in five years or so.
The modules stuff was a huge mistake in my opinion. Some people tend to like them in theory, but in practice they absolutely destroyed the adoption of newer Java versions and set Java 11 back by 10 years (literally).
Modules system was a necessary evil: it’s Hyrum’s law with the back then slowed down development of the JVM — every observable behavior of the system was hardcoded against, so the JVM devs had to build a wall against the changing internals to have any chance of continuing the evolution of the platform. Libraries that use never-agreed upon functionality are the ones in the bad here.
My issue with lambdas in Java is the complexity of types in defining interfaces that receive generic Functions as an argument. Really seems like a better definition could be used there, new language syntax possibly.
On the topic of Streams, the big issue that always crops up is exceptions. Streams and Exceptions are nearly incompatible with tacked on constructs that are painful to use. Any IO oriented program will have to face the issue of what happens to the Stream if an Exception is thrown.
Agreed. If the type of the lambda can be inferred, then they are great to work with. But if you want to define one upfront and use it later, having to spell out the type of it is almost prohibitively annoying (what do I need in this situation, is it a Function or a Consumer or Supplier or Bifunction or BiConsumer or BinaryOperator or Predicate or BiPredicate or IntUnaryOperator or ObjDoubleConsumer or LongToDoubleFunction or .........). Most of the time I'll just avoid it if I can. I think I"m the only one on my team who would ever create a local function rather than a private method, for instance.
An other thing is java's lack of tuples. All the time I'm reaching for some kind of ad-hoc grouping/touple but have to resort to using something from Apache or Spring or (perhaps abusing) Map.Entry<K,V>
> An other thing is java's lack of tuples. All the time I'm reaching for some kind of ad-hoc grouping/touple but have to resort to using something from Apache or Spring or (perhaps abusing) Map.Entry<K,V>
Record types does make that a lot easier. They aren't quite as ad-hoc as in other languages, but this:
record Foo(int a, String b) {}
is a lot less to type than
static final class Foo {
private final int a;
private final String b;
public final Foo(int a, String b) {
this.a = a;
this.b = b;
}
public int a() {
return a;
}
public String b() {
return b;
}
public int hashCode() {
return Objects.hashCode(a, b);
}
public boolean equals(Object o) {
if (o == null) return false;
if (o == this) return true;
if (o instanceof Foo f) {
return a == f.a && Objects.equals(b, f.b);
}
return false;
}
public String toString() {
return getClass().getSimpleName() + "[ a = " + a + ", " + "b = " + b + "]";
}
}
The modules system is not a blocker for migration at all. At this point, it is completely optional to enable it.
What indeed caused breakage was the encapsulation effort that blocks unrestricted access to JVM internals, and that one was not even fully and forcibly turned on until Java 17.
Oracle offers extended support for JDK 8 until the end 2030, which is more than four years longer than for JDK 11. I'd say this is the more likely reason that Java 11 is not adopted more. Spending a few hours to fire up an application to figure out the right --add-opens call really does not seem like such a big imposition to me.
Because FP is hard, and because it's easier to get 1000 Python monkeys to shit out half-baked garbage than it is to get 10 Haskell developers to agree on a set of blessed language extensions.
But when you get it right, your shit stops chewing a hole in itself in prod, and you can sleep at night and leave Slack alerts off over the weekend.
It's fine. Y'all can have your half-baked tire fires. "Industrial success" is a broken metric for "whether or not something is going to create real value for your product". We'll just keep shipping. And sleeping.
The idea of our craft is to deliver value, not to find a new weird way to do the same thing. If dentists were programmers, the most expensive ones would be removing teeth via the rectum.
> The idea of our craft is to deliver value, not to find a new weird way to do the same thing. If dentists were programmers, the most expensive ones would be removing teeth via the rectum.
What a nice quote this will make :-)
PM me the name of who I should attribute this too.
What happens when your service scales and you get a performance bottleneck? Can you.. connect to a prod instance and look at the number of allocated instances? Can you do VM-event streams with barely any overhead continuously in prod? Will you actually be able to circumvent the performance bug?
Don’t get me wrong, I like Haskell and it is a more than fine language. Nonetheless, it doesn’t have anywhere close to the tooling Java has, and lazyness/FP is quite unintuitive regarding performance. And pure FP in itself is not guaranteed to result in better results by any form of evidence we have today, and doesn’t defend against logic bugs in any shape or form over what Java can do.
Also, it’s very unprofessional to look down on software engineers using different languages then you do..
Agreed. Most of the hard problems in software engineering are completely separate from language and stack choice. Hyper-focus on these things smells of expert-beginner.
> It would be an interesting observation in 2007, but fifteen years later with no industrial success of Haskell-inspired pure FP approach
You kidding? The industrial success of the pure FP approach inspired by Haskell is that it has dragged all the other significant languages and frameworks to work in a more functional way, adding the constructs they lacked and popularizing the basic techniques.
Not "industrial success" because there were none, but rather a validity of some of the ideas. Which were successful only when adopted by some other languages, like Scala (which has a lot of issues on it's own), Kotlin or Rust.
Influencing a whole language of other industry languages to be closer to your model, that's what I would call industrial success for a radical experimental language dedicated to inventing and exploiting a new programming paradigm. Different criteria, I suppose.
The author is using field injection and claiming that the field cannot be private. As far as I know he should be using constructor dependency injection and make the field private and final. That way is also easier to write unit tests and maybe make use of interfaces in case he wants to avoid using a mocking framework.
Not only that but if I recall correctly quarkus resolves dependencies and config files at compile time and not at runtime using reflection as spring does.
It must be said that this coding style may be the norm with Quarkus (and with Spring which it is trying to replace on graalvm/native-image), but isn't required by Java itself. As always, Spring-like Java buerocrats have chosen to chase and over-engineer trivialities because it's so much easier lol; well until using your framework becomes a study in psychology or software archeology.
Update: what I don't get with Quarkus though is why did they set out in Spring factory style only to have to immediately limit this for native-image's "closed world assumption"
I'm pretty sure Quarkus tries to stick as close as possible to Java EE APIs with some limitations to accommodate the lack of runtime reflection, being Red Hat's answer to application containerization for which JBoss isn't a good fit, if it looks like Spring it's mostly because it's a popular style in Java.
I'll take java any day for server side, I don't know why but when I use java things just tend to be much more stable and It does not take me more time to write code at all.
I haven't programmed in Java in many years, except for making DAGs for Storm and a authorization metalibrary that none of the "real" Java programmers liked. Paradoxically the most sccinct statement of dislike concerned my choice of arraylike class: "THAT'S WRONG!". The Java programmers had source control, just like the rest of us. But their Maven configs weren't under source control, and they wouldn't help the rest of us with ours. I've concluded that I'm indifferent to Java, but not so good with Java programmers.
There's a big ball of puzzling contradictions in the above.
Have you heard of Lacan or Zizek? Well. Poetically speaking, I'll paraphrase Tom Lehrer and opine that "Java programmers must feel like a Christian Scientist with appendicitis". In my opinion: remember that.
They've build a lot of ritual around notions of control and deliberativeness (that array problem). They're Catholic or Orthodox, really. The JVM is supposed to be "safe", and I have to ask: does the communion wine really turn to the blood of Christ?
My father was a psychiatrist. I am well aware that Dr. Tim Leary was a pioneer in personality diagnosis, having found "The Interpersonal Diagnosis of Personality" on my father's bookshelf. I analyzed precinct level voting patterns, and they exist; but the question "is it something in the water, or new car smell?" remains vexing and unsolved.
There is something that happens when people program in different languages with different tools, especially in groups. It's not just languages: how does Docker culture compare to NanoVMs on the Leary radar chart?
But what I have for you is a (hopefully) playful personality diagnosis of some of the major programming languages. Make some popcorn. This is for entertainment purposes, but maybe it'll give you something to think about.
Java doesn't have a cultural problem. People need to understand that Java culture is almost entirely enterprise culture. Don't want a language influenced by that for your project? There are plenty out there, pick one
It has a culture of over-engineering, just look at Log4J or Spring with its AbstractSingletonProxyFactoryBean's. More complicated and far more adhoc than any abstraction should be.
Log4j is too simple to use and configure - what exactly is over-engineered and why we as users would even care if it's overengineers somewhere under the hood?
It's hard to criticize Spring Core - it's too mature and popular to worry about some edge-cases where it didn't fit the developers view of the world. As with any other software - don't use it, or improve the existing one (and Spring Core is open-source), or write your own. But now it's de-facto the foundation of the Java programming landscape.
> what exactly is over-engineered and why we as users would even care if it's overengineers somewhere under the hood?
Well, just last year we found out that you could remote load class files based on log string substitution. This was especially bad if your service logged web requests.
> what exactly is over-engineered and why we as users would even care if it's overengineers somewhere under the hood?
Log4J has been a major embarrassment for the Java community. I suggest you Google it, if you don't know why.
> It's hard to criticize Spring Core
Spring Core is at best a dynamic (reflection based) band-aid to work around Java's lack of expressiveness. It comes at a significant complexity cost and loss of static guarantees. You cannot both love Spring and Java. So which is it? :)
"The Log4j Vulnerability: Millions of Attempts Made Per Hour to Exploit Software Flaw - Hundreds of millions of devices are at risk, U.S. officials say"
Oh, please, ok, there was a security flaw - how does it prove that it is over-engineers? If anything it is too flexible and gives developers too many features so that one of them was compromised.
And that is very much false in case of log4j, it’s likely you just have a mismatch regarding the requirements of enterprise logging — far from trivial.
Of course log4j is an overkill for a simple terminal app that could just write to stderr/out just fine. But it is not an overkill at all for an enterprise business app doing millions of transactions each second with multiple instances, with n+1 kinds of config requirements. So for you to be able to call it over-engineered by your definition you would have to know whether the implementation it provides is more complicated than necessary, which I’m fairly sure you can’t know without having taken a look at the actual code.
There is essential and accidental complexity as two terms for a reason.
Log4J doesn't just log strings, it has an evaluator that can execute the strings and invoke arbitrary code. There is no enterprise that needs this feature. Describe the actual problem, and let's work out how it can be solved without stuffing a new programming language into a string.
The blog author is a contributor to cats-effect, if you want to have a look at complicated and adhoc abstractions, look no further. One person's AbstractSingletonFactory is another person's Kleisli monad transformer.
Maybe :) But I would suggest that the abstract algebra and category theory would probably be a more worthwhile investment longer term than learning about Spring Beans.
That doesn't seem to be a problem for users of those libraries: there's an hidden assumptions in the post, that every person on this planet approaching Java has to take part in its use cases. Especially FP enthusiasts and hobby projects developers. If it's engineered to where it is, some call it "over", others call it "just righty"
Yes I have worked for many banks and even written Log4J loggers that log to chat channels. But I can still admit that Log4J is over-engineered and that a security flaw was inevitable. Log4J doesn't just log strings, it has an evaluator that can execute the strings and invoke arbitrary code. Are you telling me that your bank needed that feature?
This is just an angry rant by someone describing themselves as a “functional programming enthusiast”. The post is only tangentially about Java and is really about a framework called Quarkus.
If there’s a culture problem it’s the “Look at me. I learned something new and now I’m going to do my best to burn down everyone else”. If you want to program in Haskell or whatever have a great time. Think it’s so great? Prove it by writing great software with it.
The way I see it, is people searching for a better way, after years of trying to actually use Java. And what does Java's chief architect think?
"It is my belief that the best direction for evolving Java is to
encourage a more functional style of programming. ... We're not going to turn
Java into Haskell, nor even into Scala. But the direction is clear."
You can absolutely write fairly nice functional code in Java already with a decent Either type, streams etc.
I imagine it's not that widespread because most people probably switch to a different JVM language like Scala rather than going all in on functional Java. Your functional Java code is going to be just as unreadable to a standard Java dev anyway, so the appeal of it being a widespread language is somewhat lost.
> You can absolutely write fairly nice functional code in Java already
You mean it has Lambdas? Yes that's a start, but there's a lot more it needs to claim good FP support. Programming with lambdas is not sufficient to do "functional programming", which is really about composing pure functions (everything else follows from this, e.g. immutable data). Java's roadmap does look promising though.
Pure Functions is indeed a factor, but not the only one. Other irrelevant factors are pattern matching, tail call recursion, recursion instead of loops, etc. FP is about treating data differently and dealing with side effects differently. Pure Functions are an implementation strategy of FP, not the core differentiator.
And that data/side effect difference, you can easily handle in Java, C# and other OO languages if you just overcome the culture of OO-only thinking. And despite I think the article is not perfect (who says, that a function cannot have a class/interface wrapper (aka strategy)), there is an element of truth in the spirit of the headline.
> Pure Functions are an implementation strategy of FP, not the core differentiator.
"Functional Programming" is not a well-defined term. But I would argue that if you are not using pure functions then you have more of a hybrid imperative/functional language, which is fine, especially if you are careful with how you structure your effects. Modern FP languages use monads (Haskell) and/or algebraic effects (Eff) to enable pure functions to describe effects. The pure function is what enables mathematical reasoning and safe function composition.
I don’t think there is a boolean pureness flag, rather a spectrum.
Haskell can also introduce arbitrary side effects in any code with some unsafe escape hatches, but sure, the common case doesn’t employ that, so it is a bit more pure than Clojure.
Actually it’s the other way around. There are very good persistent collection libraries for Java but if you are going to ‘update’ then you can’t store them in a final field since you get a new collection object. On the other hand the only thing final about
final List<Whatever> that
is the identity of the list.
I also think Either and Optional are cancers. Type erasure takes some of the fun away from Either. Before you had Optional you had one problem, you might have a null. With Optional you have two problems, you can get a null AND you can get an empty.
I worked at a place that used Scala and the engineering manager would talk your ear off like an Amway scam person about how much better monads were for error handling than Exceptions. I started working on non-Scala stuff but once they put me on the Scala side I found out that error handling was just missing in most places even though they allegedly did code reviews. It was definitely a frying pan to fire situation relative to Exceptions.
(The time before that whenI coded in Scala I found that people would spend 3 days writing something concurrent with Actors that only used 2 out of 8 cores and was rife with error conditions that could be done in 20 minutes with an Executor in Java.)
In a a parallel universe where LISP was the dominant language, people would be writing essays about how great COBOL and Cold Fusion and PHP are, about what a genius Noam Chomsky was, how recursive descent parsers are a force multiplier, etc.
> With Optional you have two problems, you can get a null AND you can get an empty.
That's not really the fault of the Optional. That's Java adding null as value to every type. Java also allows lists of nulls, mappings to null etc. An optional is just a container that stores one item at most. I do agree that it makes optionals less useful in Java. In Haskell and OCaml they are used essentially instead of null.
Static analysis can catch a `null` being used in a place that wants an Optional. With Optional being fully pervasive in a codebase the `null` concept could effectively be eliminated.
"The time before that whenI coded in Scala I found that people would spend 3 days writing something concurrent with Actors that only used 2 out of 8 cores and was rife with error conditions that could be done in 20 minutes with an Executor in Java"
Sounds like you were working with some folks that were just learning the language/idioms (which is admittedly a big hurdle).
I haven't used actors in a very long time. Simple future composition and reactive streams get you very far, at an absolute fraction of the code size and complexity of the equivalent vanilla Java approach (ie: not using Rx).
That sounds very close to a "No True Scotsman" type fallacy. I've done a fair bit of consulting in the past working with Scala, and my experience closely matches the one described in the grandparent post - the issues happened with veterans more than newbies in my experience (Although that's a function of mostly working with veterans and few newbies)
I have seen junior and senior devs (and myself sometimes) struggle with fancier asynchronous trickery. That's true, but I think it's shortsighted to write off that effort as compared to "just using executors" (whatever that means exactly).
For me there was a lot of learning (that often just felt like grinding), and I can confidently say that I'm leagues more productive than trying to mimic this with low level vanilla Java primitives.
I'm sure this would also apply with go channels, async/await, etc. Raising the bar of abstraction in this case can be valuable. As compared to something really high leverage like reactive streams it's just going to take a ton of code and be far less flexible in a naive approach (in most cases).
I've seen this same pattern with others. Is it worth the investment of time to get there? Not sure, maybe Go is a better set of tradeoffs for most, but I wouldn't go back to straight executors, locks, and the rest of the Java 1.5 concurrency offerings (which is what I thought the OP was saying).
> Your functional Java code is going to be just as unreadable to a standard Java dev anyway
I’m largely unsure of this. I think the standard Java dev might have difficulty adapting but the ramp up isn’t too hard. A Decent Java shop right now should be adapting functional features from Java into their code already.
I've had a good deal of difficulty getting enterprise Java devs on board with functional Java approaches.
In many ways introducing Kotlin was easier because it was clear it was a new language, and they had more openness and excitement for learning a new language.
The people behind Java always admired the ML family of languages, the Hotspot compiler and generational GC cam out of a research project intended to make ML languages run fast. In the JDK 8-* timeframe Java is increasingly looking like “ML the good parts.”
What do you mean "trying" to use? Many corporations use it with profit. Is there a better way to make money? Probably. But this way is very much used and profitable enough. So trying is not the right verb...
Yes they have succeeded in using Java to make profit, but argubly not in creating reliable, secure and easily maintainable software. Like I said, even Java's creators believe there is significant room for improvement.
I would argue about that reliable, secure and easily maintainable software.
Surely there are terrible examples for unmaintainable software monsters in every language, Java not being exempt. But based on the only relevant, existing evidence on the topic: empirical evidence, Java is probably one of the, if not the best at the software maintainability game.
Clean FP in and of itself doesn’t solve the problem, so it’s not like there is a clear cut answer to the question. I do agree that incorporating more and more immutable elements to java is a worthwhile goal to pursue, but at the same time I also believe that on a local, single-threaded scope mutability can increase understanding, so neither imperative nor pure functional is an answer to every problem in themselves.
We should strive to make both toolkits available and be ready to use the correct one for each case, the pragmatic approach.
If Java survival is to be attributed to the talent of its developers, isn't this the "Java culture" the post is talking about, which is supposed to be the problem?
The culture can both exhibit the problems author describes and create the foundation for its culture to be great at security.
Thing is, if I look at most places hiring Java devs, they are prominently places that care a lot about security. Wouldn't it be a self-fulfilling prophecy then, that Java culture cares more about security and tries to maintain higher standards? Especially considering Java was dominant for decades in places willing to pay big for security.
I just don't see why that would also mean it's directly atrributable to the language itself to the point it is 'the most'. C# exists too, it's not that different.
Go on and argue that, by providing evidence that by using Java you decrease those three metrics compared to other languages, in projects of similar size. I don't know if there are studies but if you know about them please share. Otherwise we're all talking about biased opinions, less the fact that Java is widely used and maintained.
Nonetheless they state a problem in Java culture. That should be backed by data and not by personal opinions. Otherwise they're worth what is worth. The burden of proof is on who says Java sucks in this or that way. I'm not saying it doesn't. Just that Java is there, used by thousand of professionals making money with it
And your back to arguing they should be held to a higher standard with “should be backed” and “The burden of proof”
The burden of proof rests on everyone making an argument. There isn’t a privileged stance between Java is ok/awesome/terrible. I think popularity demonstrates it’s not a terrible language like say Brainfuck, but it says little about individual aspects. Individual weaknesses in some aspect could be made up by a strong showing in other areas and being popular alone provides some real advantages.
Everything may be. You're missing my point: if someone makes a claim, as you said, should provide "proof", I think in this context one study would suffice. Otherwise he should use different tones, like "maybe", "in my humble opinion", "I'd like to know if..." but I guess it wouldn't make lots of visits to his website. I just pointed out a possibility that invalidates his claims (if proved of course). This suffices to show that a study is needed, in either direction, to say something about the topic at hand. The fact remains: Java is out there and widely used. Let me remark I'm not saying it's necessarily a merit of the language per se
I'd say Java is the second programming language that was specified by adults. (The first was Common Lisp)
The original Java memory model was broken and got fixed. It took C/C++ another 10 years to do the same.
People use Java for server back-ends because it is possible (and not unreasonably difficult) to write correct and high-performance concurrent code with threads.
The Equifax data breach was directly related to using Java specifically Apache Struts.
Banking software is generally riddled with security issues, if you assume something is secure without having it tested by a 3rd party chances are it’s got issues.
This is from 2011, maybe around the peak of functional hype, when even John Carmack sent out memos about approaching things in FP ways, and tried making his things in Haskell and then Scheme. It's my impression (which could be wrong) that he has significantly backpedaled from that since then.
What do you mean exactly by 'backpedal'? As far as I know Carmack has stated that FP in general is achievable for games, he sees the benefit, but the languages and tooling just aren't there yet. There's probably some more research that needs to be done into the ideal mix of imperative and functional features in a language, too, that's something I think is very much not settled. I believe his comments about Haskell specifically deemed it ultimately unsuitable, although not necessarily because of the paradigm. At least that's what I remember him writing and talking about ~8 years ago.
I'd say generally you lose a factor of two in performance when you use immutable persistent collections. Performance isn't everything (otherwise Python wouldn't be so popular) but when people have a system that works the last thing they want to hear is about a way to slow it down.
Can you share a timestamp? I'm not aware of any new stories from his Scheme and Haskell adventures.
In the first section of the interview he specifically points out the merits of C and C-style C++, and explains how Lisp (especially mountains of macros) are hard to maintain in a team.
He does. Not that anyone cares, but I do as well. But considering FP lessons while still writing C is a long way from considering how far Java can be moved towards Haskell. Or experimenting with writing large parts of games and systems software in Scheme.
Well, the link between whatever Carmack thought about FP and C, and Java and FP idioms, was tenuous to begin with. Yet you introduced it in your comment...
To give my perspective: Carmack was a latecomer in this, at least for the widespread programming paradigms community, and also remember game programming is a niche (so what worked there was unlikely to permeate to the general community [1]). So whatever Carmack thought (which I appreciate, since I hold him in high regard!) is unlikely to have had any sway in Java, Kotlin, Scala, and other proglangs adopting FP idioms. I distinctly remember back in that era, few people who were not gamers even knew who Carmack was (to my chagrin!).
[1] I remember an experience from my distant past, when interviewing at Vostu (a Zynga-like online game company), the chief engineer told me "we don't use frameworks here, just plain Java. Forget Spring, forget Hibernate, forget everything. We don't use them. Everything we do, we do by hand". (I didn't take the job offer for unrelated reasons). Note this is highly unrepresentative of the Java community at large!
Java needs to be more flexible and expressive, that's all, type inference, traits,collections,function expressions ... some things already made their way into the language after years of inflexibility as a philosophy.
If the language improves, then frameworks and API can be made less verbose.
A lot of "design patterns" in Java were created simply because of the language lacking expressiveness and are thus obsolete now.
Most design patterns come way back from C++’s OOP hype area, actually.
Java just made those insane architectures possible due to not segfaulting at every turn, hence it also got associated with them. And indeed, many are “deprecated” by all the “new” features, including generics which is not exactly new.
It's not about just being more expressive. See the Brian Goetz quote. A more "functional style of programming", this means less mutation, more pure functions, e.g. Java's proposed records are immutable.
In general Java has picked up many features of the ML family of languages such as pattern matching, sealed classes, etc. It is so much fun to write compilers in Java using this stuff.
I code a lot of Python also, and I've been pleased to see pattern matching adding to Python by a process that seemed very similar to that in Java, that is, in both cases the feature was developed gradually with a lot of care to make sure it works well with the rest of the language.
Code Annotations are data (code = data). Data which is used in a function (side-effect-free) which validate a record. I do not know how more functional a generic purpose validation function can be.
The problem for people, is that they do not like the place where the function is called and side effects are applied. In your typical Java/C#/... OO framework, this function is called two or three stack frames down into the framework. And here the author's point of Java/C# culture has its point.
Instead he expects interfaces to do magic. AutoClosable isn't even one of Javas pre annotation marker interfaces and the documentation makes it clear that just because the interface is present doesn't mean that a call to close makes sense.
Email address validation isn't magic and nor is annotating fields to give more context about what they're meant to contain.
The article author believes everything should be done with types. But Java has always been able to represent an EmailAddress type. It's not like the Quarkus guys don't know about strong static typing! Bean Validation exists because:
1. Every tool knows what a string is. Very few know what your custom email address type is. In theory you can solve this with sub-typing, or duck typing, etc. But in Java the String class is final, so you can't extend from it. This has definitely had big advantages over the years - there's no proliferation of custom utility string types, and new Java versions could optimize the internal representation aggressively. It's not at all clear that Java would be better if String was an open class.
2. Bean Validation lets you validate every field at once and collect the errors together. This is extremely helpful for user interfaces where you want to be able to copy user data into an object, validate it, and then return all the problems to the UI for rendering next to the form. With the type approach he gives you'd get exactly one problem followed by an exception. The user would fix it, re-submit the form or re-execute the tool, and then get the next problem. Painful! Ditto for APIs where you want to de-serialize JSON into objects, validate them all in one go and return a single error response if there are issues.
3. Bean Validation is designed to be applied to both database entity and GUI model (e.g. config) objects, though it can be used for anything. Immutable types are useless for this use case because the database and GUI are both fundamentally about mutable state. The DB layer can generate far more optimized SQL and thus far more optimal DB resource usage if it can record what's changing (which requires some sort of "clever" stateful object), and for the UI you need to be able to hold invalid data somewhere - ideally in your model object, so the user's data can both be held in the model and also trigger validation errors.
4. It can also be used with parameters and return types on methods (with sufficient framework support from your implementation). This lets you add constraints to implementations in an API compatible way. Changing types wouldn't allow for that.
The author should really just read the intro to the Jakarta Bean Validation spec, which lays out the rationale quite clearly:
Does anyone worry about whether reading a value out of a config object will launch rockets? No. The type system could express this, but the value of doing so would be very low. It is obvious from context. The reason it uses interfaces is because, if we check the docs:
"Under some circumstances, some data sources may change dynamically. The changed values should be fed into the client without the need for restarting the application"
If you implement config handling in the FP way he wants then a change to any part of the config requires rebuilding the entire object graph representing the entire configuration, and then re-plumbing that all through the rest of your (presumably immutable) set of objects. What you'd intuit should be a very fast operation with little memory overhead can suddenly require re-initializing large chunks of your app. A functional programmer might simply assert that being able to mutate configuration at runtime is a bad feature, but in the real world we do this sort of thing all the time and being able to handle it well is a useful feature of a config library.
The core disagreement here isn't actually anything to do with Java. More or less any imperative language would attract the ire of this author. What he's upset about is the existence of libraries and frameworks designed for mutability. But, mutability is inherent to the world. You can try to manage and constrain it but you can't remove it and sometimes you have to embrace it. That's not "Java culture", it's just life.
AOP should end at `@Override`, all the rest turns Java into some kind of DSL which makes it unreadable (similar to what some people do to Scala, because it is too easy to make it into a DSL).
Poster child of AOP is logging, you log stuff, don't change the code (e.g. adding validation).
You can’t change code either in Java — annotation processors are explicitly unable to do that — Spring’s magic works through loading new classes up that either implement the same static interface you use, or extending the class, and injecting this new proxy instead.
In short, if you want to log certain methods at enter and exit time with AOP (not manually adding it everywhere), you just create a proxy object and do a logEnter(); targetedObj.method(); logExit();
That’s why some people dislike Lombok as that actually changes byte code at compile time.
The bytecode format is well-documented. If Lombok would just augment these, then there would be no major problem with it. But it actually does something worse: it uses reflection and other tricks to extend the Abstract Syntax Tree, which is purely an implementation detail of the Java compiler. And it must do the same with every tool that touches Java Sources.
He is the author of Monix, a great concurrency library in Scala and many other OSS contributions. Personally I think it's one of the best libraries ever. It actually exemplifies a very pragmatic mix of OOP and FP that decidedly leans away from trying to recreate Haskell unlike a lot of other Scala libs.
We're about 1 year into development of a Quarkus application and I'm still a big fan. The fast startup and hot-reloading are amazing for rapidly iterating. Most importantly, it's really easy to reach a Quarkus developer if you have any questions. They also have a YouTube series "Quarkus Insights" and they do live stream events where you can ask questions.
It's true the documentation initially feels a bit lacking for the Getting Started scenario. But now I really appreciate the Guide format (essentially How-To's). Whenever I need to accomplish something, there's a guide for it.
With that said yes I still wish there was less magic in the Java ecosystem. Some years ago I toyed with Clojure a bit and I had some issue related to sessions with the popular Ring HTTP library. I viewed the source code on Github and was amazed that it was all vanilla Clojure. And even for a novice Clojure dev it was written in such a basic style I immediately understood how the whole library worked and found the solution to my problem.
Exactly. They didn't even look for other frameworks either. If you want something that looks less like Spring, you could at least try out one of the kotlin frameworks like ktor, which is still totally accessible for java developers.
I'm still waiting for ASP.Net-like framework in Java where all configs are explicit and the framework doesn't require hundreds of confusing annotations just to display a hello world page.
Have you come across Dropwizard? It's referenced in this article too as an example of a Java framework that embraces a more 'simple code' approach and that has certainly been my experience with it. Recommend you give it a shot!
That's actually trivial. The problem is that NO developer wants to read 30 minutes to understand how it work so they bitch online how "its complicated".
Also this is just weird, it tries to be EE but it just ends up being convoluted without applying the patterns correctly. E.g. all the logic is executed in the constructor of HelloWorld.java - I can't imagine that ever getting past code review.
Well it's on gh so why not fork and fake a PR with your code critiques (plus lots more, such as heretically not using a symbolic constant for zero) to make the satire perfect?
This is a dumb rant about something the author doesn't know anything about and hasn't bothered to understand or research.
Dependency Injection is not functional programming. Dependency Injection solves a lot of problems and entire classes of bugs around application state, but with far more approachable stepping stones than something like pure functional languages.
Pure functional languages solve state problems as well but are far more complicated and difficult to grok because of the paradigm shift.
Whatever one chooses for a team of developers, the most important thing is being able to use the tools at hand. It's pretty easy to explain to someone what Scope is and how CDI has some standard scopes.
The author doesn't even seem to understand how Java annotations even work.
I found this interesting because the ConfigMapping stuff reminds me a lot of Python. I knew that Python OO was inspired by Java OO to some extent, but I didn't expect so much similarity.
I think Python could learn similar lessons here. But the flipside is that none of this stuff is a catastrophe in Python either, and the author's tone seems a bit out of proportion with what they're complaining about.
The author complains about:
1) not knowing the underlying data model and implementation details that might affect their design
2) mutable attributes and non-final classes
3) lack of obvious composition root
In the Python world, we often say that "we are all consenting adults here".
Usually there is no need for private, immutable, or final things because people read docs know not to stray from the public interface. The trust also runs the other way, and it's very rare for the function to perform some kind of weird unexpected mutation. A function that mutates a user-provided object without the user's knowledge or consent is usually considered a bug. I actually found exactly this bug recently in a framework I was using, and several people were up in arms about it on the issue tracker. Yes, immutable data makes it harder/impossible to write such bugs. But the community generally has not devolved into a Satanic orgy of mutable state.
Likewise, if a framework did something ridiculous like reloading a config file at every request, nobody would use it! I could only imagine how lively that GitHub issue thread would be. A framework that makes fundamentally bad design decisions tends not to be a lasting part of the community.
The only one that really would suck, and that also doesn't seem to be a problem in Python, is the lack of an obvious composition root. I have never heard this term before, but it is a very very well-established pattern in Python application and library architecture. A framework that made it hard to design libraries in a conventional sensible way, would not be a very popular framework.
> Usually there is no need for private, immutable, or final things because people read docs know not to stray from the public interface.
My experience is that this fails as soon as you've got a modest number of users. It only takes one user doing something foolish and now you've got a fight on your hands when you want to change an internal component of your system. And you'll often lose that fight.
Fair. Perhaps an implicit limiting factor in the Python community is that Python applications tend to be written and maintained by smaller numbers of people concurrently.
But I think there is also a strong culture (at least nowadays) of individual developers not fucking with library internals and generally respecting API boundaries.
Problems arise mostly with libraries that lack a clear public/private distinction like Pydantic and FastAPI, which is one of the big reasons why I personally think they are (currently) unsuitable for serious professional application development, despite being big winners of the "framework popularity contest" and being excellent tools for rapid prototyping and development of ad-hoc things.
What does one do to land a Java job when neither Spring, nor Quarkus experience is present?
Backstory: I have been using Java SE for more than a decade now. Currently building a Java 17 pure Vert.x project. I'm quite seasoned with the language, but not with large frameworks like Spring. I don't have any desire to learn Spring. Quarkus seems like a good choice on the first look, but I'm also looking for other options.
I quit software altogether after programming in Java for about 7 years and decided I never wanted to program again. Then, a few years later, I discovered Python and jumped back in with delight. Now I have discovered Elixir and I'm super happy. But to this day I can't even look at Java code without that dying-inside feeling.
There's even kofu, which largely get rid of annotations entirely in favor of declarative Kotlin DSLs. Very similar to how ktor works. I tend to use Spring this way as well. E.g. we use the router dsl rather than having annotated resource classes. Kotlin DSLs are superior to using annotations IMHO. More explicit/declarative, similarly easy to read. And it gets rid of all the builder boiler plate that you have in Java.
Annotations are definitely overused in some frameworks. And debugging them is a bit tedious when the magic breaks; which tends to happen with the more complicated stuff. I've seen some spectacularly misguided things happen with e.g. the @Transactional annotation having some not quite intended side effects. I always preferred just using TransactionTemplate. That makes the transactional boundaries much more clear than junior engineers copy pasting annotations until stuff vaguel stops breaking. Which, btw. is literally what people seem to do with @Transactional. I've cleaned this up in several projects.
Annotations with complicated side effects are a bad idea; especially when you need to understand what those effects are.
I find the Java 17/Good IDE quite nice. Most of all the complaints have been resolved with the language, and it’s still faster than every other memory safe language (except Rust, which smokes everyone)
"Java is good by modern standards, from a technical perspective, the platform having received a lot of improvements from Java 8 to 17. Unfortunately, it still stinks, and the problem is its "enterprise" culture."
Then the author rants about a specific Java-based framework and does not mention anything about the use of Java in the enterprise or any supposed cultural problems it may have.
Issue is that Quarcus advertises as the new, hip framework but what it does it still reuses the old pre-17 constructs. It should use the latest and greatest from Java if it wants to be a game changer, not repeating what Spring did with their support for old, deprecated JDK releases.
They’re not talking about Spring/Boot or Micronaut though. There isn’t even any evidence the author has any more experience with them than what little they have with Quarkus. It’s a rant.
I don't get, why author is bitching about validation.
What's the point in referencing article about Haskell, if Java don't have such parsing libraries.
I've also saw that article translated to Rust, but it ended being article, not library. And many questions of how to represent valid state remained unanswered.
And example provided, with throwing exception in constructor: that approach would just throw single error, but javax.validation approach allows to return list with ALL failed validations, so client don't have to fix errors one at a time and retrying to see next error.
Small complain from me: EmailAddress class is not zero cost(unless java would have value types), but actually java devs do not care about this.
Shall we definitively say that a problem of all languages is that they evolve to the maximum complexity that a single brain can understand, then they collapse because nobody understands them?
All languages start with “Lightweight, lightening fast and hotreloadable!”, then become a gas plant as they need abstractions to handle industry concerns (hello Reflection API, javaagents, NIO, functional programming, now the Cloud, then AI),
Are there intrinsic properties a language can have that prevents bloating to the maximum complexity a human can handle, and yet allows it to handle industry usecases?
Java is absolutely not victim of language-complexification by adding the n+1th fancy feature of the day, if anything it is one of the few exceptions — it is a quite old language and yet modern Java has very little differences to what it was back than — all the spring magic happens through its standard way of operation, everything is a class with methods, and these primitives are assembled in novel ways, that’s it, and that’s how it supposed to be. No async, coroutines (it gets solved on the JVM-level and exposed through method calls), no separate syntax for stream operations (those are just method calls with “builder pattern), etc.
I greatly recommend Guy Steele’s Growing a Language talk about this exact topic.
Indeed! The 'Complexity Horizon'. Any constructed system will tend to that horizon as it continues to be developed (particularly if by multiple people over significant timescales) - unless there is a concerted effort to pull it back.
I love the ‘complexity horizon’ of algorithms: If something can be made simple, then we’ll build more complex things until we can’t understand it. All algorithm are always right above the maximum complexity that a single human can handle.
Actually I believe it’s another surprising emergent effect of open-source: Linux’s or Postgres’ complexity horizon seem to have been managed, so that adding new stuff didn’t increase complexity, and it’s still possible to add new stuff THIRTY years later.
You don't have to follow a framework explicitly to have success. You can use vanilla components of a framework and then treat the entire application more functional esque. The author's examples are just so overboard. Annotations on fields to represent validation is valid and I see it all over JavaEE, but I've never thought of as my "go to". (Or else you are then locked in to how to represent return values, relaying messages, entry points).
Live by the framework, don't die by the framework.
So you think there's some magical language, where things will never become complicated, syntax will always make simple sense and no developer can f** things up?
Think again. No such thing. Java is a tool, and like every other tool - you have to know how to use it if you want to get the best results from it.
When used by the right developer, Java can yield beautiful, concise and clear code. When used by the wrong developer... well...
This thread makes me smile as an Android programmer.
Android doesn't have the cultural problem discussed here, but it has its own set of problems (mostly rooted to the poor platform API design). It's so contrastive. Which do you like?
It's not culture, it's the fact that Java enables huge structures to run that has become it's problem. Listen to James in the last chapters of this interview: https://www.youtube.com/watch?v=IT__Nrr3PNI
Just because Java can run bloated is not a good reason to do so. I have written my own HTTP/DNS server with built in DB from scratch and it's 150KB (around 10.000 LoC) and that's how you use Java properly.
> I have written my own HTTP/DNS server with built in DB from scratch and it's 150KB (around 10.000 LoC) and that's how you use Java properly.
So you've probably got 90,000 bugs left to fix then.
Seriously these "I built my tiny thing I claim implements X protocol" are very tedious. You think BIND is deliberately overcomplicated or do you think, maybe, just maybe your tiny server has lots of hidden problems.
I was going to post a comment about the regex used to (coarsely) validate email addresses made no sense, but when I opened the article in a new tab to copy for quoting, it had changed. :)
It's now
"^[^@\\s]+@\\S+$"
with a comment also stating that it could be better. That's at least sensible, the original one that I first saw was more or less "^[^@]@.*" which kind of makes no sense. Oh well, great that the author caught it, of course.
I couldn't agree more. I currently work on a web application that uses Spring Boot and Vaadin and it sucks a million miles. Spring sucks separately from Vaadin, which sucks another million miles.
The whole thing with beans and annotations is an abomination and whoever thought of it should be shot (not literally, for crying out loud!).
I strongly dislike working with most Java frameworks because I disagree with many of their design decisions (and type erasure keeps finding ways to bite me in the ass) but I accept that when I use Java, I need to comply with its expectations and work with the ecosystem.
This reads like a functional developer who believes that functional programming is The Only Right Way to program anything trying to shoehorn functional programming into a framework that's decidedly not functional. Sorry, but that doesn't make sense.
I agree with some parts of this; the lack of proper nullability in the type system is very annoying especially once you've worked with more modern languages. The conflict between recommending the use of `final` and major libraries being incompatible with such use is also quite jarring (though there are alternatives). However, statements like
> This should be a pure data structure;
go unexplained, assuming that "every FP developer" should know this for an absolute truth because... why, exactly? I can easily see these methods have side effects because they read configuration files, for example. Hard-coding IP and port in the Java files is a terrible idea. Sure, the record based abstraction is nicer, but records are not supported for a large part of the JVM ecosystem because upgrading Java versions is slow and painful for larger applications.
I also don't understand this "annotations are bad" argument. Yes, some annotations should be built into the type system (and there are projects to accomplish this!) but what's wrong with @NotEmpty? Furthermore, the email address solution uses Objects.requireNonNull(value) without any annotations, making it difficult if not impossible for IDEs and static code analyzers to determine the nullability of this field. It also throws exceptions without any indication that the constructor can fail (I know many Java developers don't like the `throws` keyword but if you're going ham on code style and semantic programming, at least be clear what can and cannot fail).
Java is not a functional programming language and the ecosystem even less so. It's an imperative language with functional aspects inserted to make your life easier. Projecting functional paradigms onto it is impractical at least, or maybe even just plain wrong; it's the wrong tool for the job.
I personally find many of Kotlin's paradigms get much closer to the functional programming paradigm than Java, especially with the new libraries and frameworks that make use of Kotlin's language features. Kotlin is as imperative as Java but it's val/var system combined with (data) classes compiling to "normal" Java classes make it an excellent candidate for immutability constraints, stricter types, and other such annotation replacement language design. As an added bonus, the language works quite well together with most Java libraries.
> Java has null in it, all object references can be null, and it’s too late for that to change.
At Facebook I used Hack, which is a fork of PHP. They've spent a ton of time on the type system to the point where I'd argue it's very good. One of the best things is that nullability is built into the type system so for:
vec<?string> $a; // alows nulls
vec<string> $b; // does not allow nulls
function foo(string $s) {... }
you get:
$a = $b; // this is OK
$b = $a; // error
$b = Vec\filter_nulls($a); // OK
foo($a[1]); // OK
foo($b[1]); // error
if ($b[1] is nonnull) {
func($b[1]); // in this block the type is 'string' so this is OK
}
func($b[1] as string); // OK but throws a runtime error if it's null
I was surprised just how useful these seantics are and how many errors could be avoided this way in annotated code (there are an ever-dwindling number of corners of the FB code base that aren't property typed).
Like Java, this operates on type erasure at runtime so there are limits to the protection. I hope other languages with less advanced type systems (and I include Java when I say that) adopt nullability as a first class type concept.
There's a lot of talk here about dependency injection. This was a big innovation in Java 20+ years ago. For some reason no other language seems to collectively wring its hand about DI quite like Java does.
Java does suffer from a lack of duck typing, hence all these typically one use interfaces get created. It also has other issues (eg you can't make your own class that'll be a snap--in string replacement). Guice I consider to be a form of torture.
Maybe it's time Java decides to stop rigidly solving a prroblem no one else seems to have. Maybe that part of a cultural problem.
As for the "enterprise" problem, is that specific to J2EE? Is that still a thing? Or is just the consequences of the patterns J2EE promoted that still propagate (eg rigid DI)?
It's generally better to have some structure rather than none, even if it's a bad structure. Maybe I just don't travel in the circles where you see a lot of bad Java enterprise code?
Java has warts but the runtime maturity in particular is still world-class. I wish Google had bought Sun back in the day (I bet Google does too given the costs and consequences of the lawsuits) but Java continues to plod along and get better without getting massively complicated (I'm looking at you C++) so it all seems mostly fine.
>> no other language seems to collectively wring its hand about DI quite like Java does.
C# is the same. I don't think DI is justify-able in many situations unless you intend to inject alternate, non-test versions of dependencies. If only injecting test stubs and mocks, it is important to remember that you are creating various new implementations. The stub/mock implementer needs to have a deep understanding of it and the mock also needs to behave like the real thing (which could easily diverge). The value depends on the behavioural stability of the dependency being mocked/stubbed.
Re: better null handling, languages like C#, Kotlin, and TypeScript all do similar things and IMO Kotlin in particular is a very nice experience while retaining Java interop.
Sorry, it is not worth reading and analyzing. You can come up with a lot of criticism about Java (just as about any other technology) but the content is just too shallow.
> One of these days I’ll find out what the heck is a “bean”.
It does indeed show a confusing lack of Java knowledge.
It is quiet strange that this tidbit was in it, as I'm not sure how it's even possible to not know what a bean is if they've ever worked with the language for any amount of time.
Unless it was meant as a sarcastic remark as the term is sometimes used to express what they enable (eg. singleton services) vs what they are
It’s a standard and it’s similar to a naming convention, but instead applied to class structures.
Instead of extending/implementing some strange, third-party classes, you create a POJO (plain old java object) and you add getters and setters for the fields you want to make externally reachable.
Now tools that don’t know anything about your code can use them through reflection based on these conventions, this is how dependency injection, JPA, every Java EE/Spring thing operate.
Learning Java once is easy. Understanding it is fine too. But learning new framework every time you switch projects - not so much. Because every time it becomes a gargantuan task, with all the dependencies that it pulls. And the moment you wants to do a tiny step aside, you need to learn intricacies of internal working of said framework just to something oh-so-slightly different than original authors envisioned. And then add couple more libraries, and endless conflicts, and stubs, and injection debugging nightmare. The maintenance of the dependencies becomes a full time job...
Yeah, Java is simple. Everything else around it makes it almost unbearable with mental load that you need to keep just to move forward with a simple change.
The author shows good taste referencing Dropwizard. Then shows deep (even dangerous) inexperience with his email type. So while I encourage writing like this, and I especially like the exercise of considering all the different ways configuration can make it into an application, I don't think this piece would be worth referencing in, say, a work argument.
Actually the email example is the best from the article, validation using external libraries is a hell and I really would like my codebase to get rid of it.
Reading between the lines, there seems to be a significant hope that an easy solution exists. Maybe a young programmer? The kind who could write the EmailAddress class because they didn't laugh adequately at the very idea?
> Java allows null objects… that ship has sailed
Not necessarily, with project Valhalla, it should be possible to have generic Value Types in the language that would allow things like Optional to be defined on the stack where it would be non-null and then all it’s methods for checking if the Object it references would make sense, and actually work. This would be in contrast to today where the Optional type isn’t as useful as it seems since it can also be null.
Anyone know the status of this? https://en.m.wikipedia.org/wiki/Project_Valhalla_(Java_langu...