Java’s Cultural Problem
alexn.org
alexn.org
> 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...
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.
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
Could you cite some? Specifically any that encourages immutable objects (like records in entities).
And yeah, its licensing is basically it is free for open-source dbs, paid otherwise.
* 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
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.
I would stick with JDBC if I were the only developer.
Developers are lazy and just throw hibernate everywhere.
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.
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.
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.
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.
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
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.
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.
- 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)
Go is typically out of the picture.
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.
> Those are all fair points, but more often than not, they have little-to-nothing to do with current discussion.
but earlier you noted that:
> you can't really dissociate the language from the environment
So it seems like a self-contradictory position.
Why can't I say:
"I like how C# implements XYZ"
instead of
"I don't like the C# development environment, but I like how C# implements XYZ"?
What value is there in qualifying every aspect of a discussion given that you cannot fully dissociate <A> from <B>?
There is one other non-VS, non-Rider alternative: Visual Studio for Mac.
> 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.
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.
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].
[1]: https://javalin.io/
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 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.
Tried SparkJava and later decided that Javalin is a better choice which is now what I reach for. Also using JDBI to go along with it.
> Helidon Nima might be a saviour to me
Well, Helidon Nima is getting a Loom-based implementation; so you'll get to keep performance without the reactive headaches.
> 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.
You can load configuration programmatically if don't want to use dependency injection and/or want something that is easier to test. See https://download.eclipse.org/microprofile/microprofile-confi....
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... ;)
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.
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.
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.
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.
Well that's the article's premise, right? Opening sentence:
Java is good by modern standards, from a technical perspective
Later on: Java’s culture eschews common sense approaches
> so it's not much of a problem today.Every Java codebase I've worked on in my career has had plenty of this crap in it. The culture has settled on doing it this way.
Typical immature enterprise organizations tend to extend the system with low-risk new features instead of refactoring and remaking old stuff.
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.
[1] https://docs.djangoproject.com/en/4.1/topics/http/shortcuts/...
Why? A shortcut like that is useful all the time when writing URL handlers (views in Django).
@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 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.
[1] http://worrydream.com/refs/Ingalls%20-%20The%20Evolution%20o..., §5.11
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.
Are you sure? Are there any demographics stats on that?
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.
So you're saying it was just Sun and not IBM?
"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]"
https://en.m.wikipedia.org/wiki/Distributed_Objects_Everywhe...
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.
https://redmonk.com/jgovernor/2005/11/07/time-for-ibm-to-res...
https://en.m.wikipedia.org/wiki/Jakarta_Enterprise_Beans
Might it have been a combination of both?
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!
Which is also true for node projects I guess :D
And the metaprogramming comment stands IMO.
+1, I love this article. Do language developments since 2014 invalidate any of his characterizations?
I doubt many still use Swing, anyway.
Also, are you insinuating that those who “get” OOP can't find FP a superior approach for certain problems?
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.
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.
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.
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 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.
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).
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.
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 + "]";
}
}Never blame people who try to solve real problems with your stuff. Better get your ego out of their way.
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.
You can see how such features are not super popular to the extent of widespread non-adoption of Java 11 even 10 years after its release.
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.
What a nice quote this will make :-)
PM me the name of who I should attribute this too.
When do you actually ship though?
> monkeys to shit out half-baked garbage
> your shit stops chewing a hole in itself
> half-baked tire fires
Awesome. Terrifying, but awesome.
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..
it's a major red flag for me when giving or taking interviews.
Okay, when FP people get it right, they can let the rest of us know and we'll jump on the bandwagon.
Until they get it right[1], let the enterprise devs do what they need to without pouring scorn on them.
[1] For whatever meaning of "right" you have.
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 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.
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"
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.
'Poetically speaking, I'll paraphrase Tom Lehrer and opine that "Java programmers must feel like a Christian Scientist with appendicitis".'
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.
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? :)
Over-engineering: "the act of designing a product or providing a solution to a problem in an elaborate or complicated manner"
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.
Nope, it was not
https://www.wsj.com/articles/what-is-the-log4j-vulnerability...
> If you cannot admit this, then perhaps you are part of a "cultural problem"?
I'm sorry, but this edit from you is just stupid.
The "hundreds of millions of devices are at risk" thing happened only because log4j is used a lot. That is pretty much opposite of embarrassment.
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.
Believe me, there is very little in spring/java ee that is completely without reason, they didn’t just like overabstracting shit for giggles.
Perhaps these are both the same thing.
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.
"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."
https://mail.openjdk.org/pipermail/lambda-dev/2011-August/00...
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.
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.
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.
Every online banking system I've encountered (4+) was built in Java. None of them have been breached.
Please cite a counter reference
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.
Are there any languages for which this isn't the case? Of those, are any of them in relatively common use for production environments?
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.
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.
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.
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.
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 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.
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.
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.
var result = map(x,x=>x.y)
which is equivalent to var result = isNull(x) ? null : x.y
where isNull is static imported from Objects.An approach which is authentic to some other languages is to consistently used a list with 0 or 1 members instead of optionals.
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).
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).
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.
"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’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.
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.
- Lot of inline methods in stdlib alleviate the performance penalty of using higher order functions for tight loops.
- It is nice to have everything as expression
- Extension functions, it parameter, operator overloading, tuples are all micro-conveniences that coherently add up to a very pleasant experience.
I think the author's experience would have been substantially different if they used ktor/http4k/vertx with kotlin.
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.
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!
Is there a reference for this?
If you think he is "an AR programmer" you may want to re-read his bio...
I say C# more than scala as it odd a little more mainstream.
Do you mean Self and Smalltalk? Because the Hotspot compiler came out of the work on them, not ML languages.
This seems like an investigation into "How does typical Java code look?", not "What's the best possible way you could write this on the JVM?".
The example with email validation really hits the homebase really well. Also dependency on old java versions (without records) is really troubling.
Fortunately Spring 6 will depend on JDK 17.
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).
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 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:
https://jakarta.ee/specifications/bean-validation/3.0/jakart...
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.
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.
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.
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.
It was quite a pleasure to use.
Perhaps mostly driven to be able to (more easily) use graalvm to generate native images.
This issue is blocking half of the team, please fix asap"
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 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.
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.
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.
Because honestly at this point I'm pretty much all caught up on that.
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.
Hints anyone?
Spring also requires fewer annotations overall and none of the CDI ones.
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.
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.
A person understanding what Java EE is wouldn't write such a rant post in the first place ^^
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.
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?
I greatly recommend Guy Steele’s Growing a Language talk about this exact topic.
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.
As opposed to Excel.
Live by the framework, don't die by the framework.
This hints at a junior developer. Once you have configurations depending on environment or geography you'd care a lot.
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...
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?
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.
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.
It hosts a MMO with 350.000 customers at 10x perf./watt than all AAA studios.
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.The whole thing with beans and annotations is an abomination and whoever thought of it should be shot (not literally, for crying out loud!).
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.
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.
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.
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
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.
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.