238 comments

[ 2.3 ms ] story [ 106 ms ] thread
I think java made some wrong decision, with bigggest one being very pragmatics generics-addition, resulting in trouble down the road like List<int> not being possible. But I don't think checked exceptions are one of them ;) I don't like too much flexibility in a language that has strong types. If you undermine the type-system, why not code in python? But some additions of Lombok seem worthwile.
Checked exceptions have often been presented as a mistake on the basis of the ergonomics and workflow problems they cause, not because they are unhelpful when implemented. The problem is that people don't want to break their flow to deal with them, so they don't deal with them by catching/throwing exceptions improperly.

EDIT: There's also the fact that Java includes unchecked exceptions, so you can already subvert that part of the type system.

I don't really write much Java these days, but I've use at least a few libraries which use checked exceptions in a way that really sapped the fun out of the experience. (And personally I feel it is important for writing code to be fun, because little else of working with computers is.)

I've never understood the arguments against checked exceptions.

"Forces API clients to catch exceptions you can't handle."

My observation is that obfuscation frameworks throw a bunch of checked exceptions which are unrecoverable. Which says more about the frameworks than the language.

Hibernate: RDBMS obfuscation framework

Spring: Flow of control obfuscation framework

JAXRS: Exception obfuscation framework

I've had both good and bad experiences with checked exceptions - it can really depend on the team!

One of the parts of Lombok that really works well for me is that its notations are completely optional and fit many different levels of granularity as you see fit. Don't want sneaky exceptions? Don't use them!

I mostly end up using it to make my Java code feel like C# though...

The problem with checked exceptions is that they constrain implementations by what possible failure states they could have. I.E. the implementations of an interface with a method in it that declares no exceptions will either never call anything that could throw a checked exception, it will internally handle its checked exceptions, or it will wrap them in an unchecked exception. In practice, the first is a straitjacket, the second leads to ignoring errors you can't propagate, and the third is boilerplate that makes it harder to actually catch the exceptions when you need them.

What would've made them good would be a static analysis of some sort that could divine all possible exceptions thrown by a method call and its dependents based on its code, but in Java a class can always override another class and do something entirely different so that's not possible.

Try reading this aloud: "The problem with return-types is that they constrain implementations by what possible success states they could have."

Checked exceptions are (for the most part) the mirror image of checked returns! Both describe and circumscribe your expectations for what comes back from the method, the only difference is one is happier than the other.

They follow the same kinds of type-rules, and the real problem is entirely on the human end: It's harder to imagine failure scenarios, features are prioritized over error-handling, and people don't like to do it if they can avoid it somehow.

> What would've made them good would be a static analysis of some sort that could divine all possible exceptions thrown by a method call and its dependents based on its code

Again, ask yourself "why don't we already do this but with return types?" Probably because it's horribly impractical in most cases and and outright impossible in others. For example, when it comes to interfaces, abstract-methods, or overridden methods... All of those involves concrete implementations that haven't been made yet.

Heck, they might not get created until months or years after you ship your .class files. Then some random guy you've never met says: "Oh, hey, I can fix my problem by creating my own implementation based on their interface..."

So unless your compiler can literally see into the future, you've got to set down your type-expectations first.

> but in Java a class can always override another class

No, even if all classes were marked "final", you'd have the same problem when it comes to using interfaces.

> I don't think checked exceptions are one of them

While they may not have been the wrong decision, empirical evidence continually shows they add a syntactical burden many devs just work around (e.g. just wrap in a RuntimeException anyways). It's understandable why most modern languages don't consider predictable exceptions as part of the function signatures (though many discourage exceptions altogether in all but the most extreme cases).

This is similar to saying that since error codes are ignored by most developers, functions shouldn't bother returning them at all.

Error conditions captured in the type system are a vital part of making code robust. The fact that some developers don't know how to properly handle these error cases doesn't change anything to the soundness of checked exceptions.

> This is similar to saying that since error codes are ignored by most developers, functions shouldn't bother returning them at all.

It's not like saying they shouldn't return them (unchecked exceptions exist), it's like saying they shouldn't be forced to check the error code or declare that they will return that error code themselves. Checked exceptions aren't sound when in practice the same ones are reused to mean many things. And we shouldn't pretend deciding whether an exception should or shouldn't be checked is an objective choice applied the same way across the ecosystem.

Checked exceptions are the compiler forcing the developer to consider error cases.

What's not to like about that?

Contrast with all the other approaches (runtime exceptions, return codes, Either/Result, etc...) where the developer can happily ignore errors, the code will compile, and then crash at runtime.

I'd be happier with checked exceptions if Java assumed that

  list.stream().map(doStuff)
might throw whatever doStuff might throw, but instead map is defined so that doStuff can never throw any checked exception, which requires smuggling out every real-world failure mode.
And while we are at it, @SneakyThrows by Lombok.
The problem with checked exceptions is that any method can throw runtime exceptions, whether it declares checked exceptions or not. So the presence or absence of "throws" does not really change your approach to error handling. An exception might happen either way, so you need to exercise the same level of care either way.

Furthermore, checked exceptions force you to bubble up implementation details all the way up to the interfaces. For example, if any implementation of an interface might throw IOException, you need to put that in the method signature in the interface. You can be clever and wrap it in a custom exception that exposes the same level of abstraction as the interface, but that's not a whole lot better than just using RuntimeException, and it causes people to try to cast the exception cause.

Your second paragraph has a direct-equivalence to complaining about return-types and advocating that everybody should just specify "Object" to make things easier.

( https://imgur.com/iYE5nLA )

In both cases it's about constraining and describing the results from a method. The only difference is that most developers are so much more accustomed to thinking and coding along the happy-path, where they will willingly do all the exact same architectural tasks.

Seriously, read these three variations aloud and see how they sound:

>> return-types force you to bubble up implementation details all the way up to the interfaces

>> if any implementation of an interface might return Foo, you need to put that in the method signature in the interface

>> You can be clever and wrap it in a custom return-type that exposes the same level of abstraction as the interface, but that's not a whole lot better than just using Object

I'm willing to bet you can think of a slightly-incredulous answer to each of those, right? Perhaps something along the lines of "no they don't", "yes that's normal" and "WTF of course you should."

Declaring only some of the things that might go wrong, which is the case with checked exceptions, does not solve the problem you described and only makes it more difficult to write code.

If every kind of exception was checked, then every method in Java, including constructors, would need to declare that it throws RuntimeException and IOException and NullPointerException and NoClassDefFoundError and InterruptedException because those things could happen in any method.

So we settle for declaring the type a function is expected to return, with the understanding that if something exceptional occurs a magical thing called an Exception will be thrown and it's not handled the same way as a return value. This lets us get our jobs done. It's engineering, not science.

When you solve the halting problem we can talk about formalizing every possible failure state as a return value. Until then we will get on with our lives.

> If every kind of exception was checked [...] When you solve the halting problem

Hold it: I said nothing remotely like that. You have begun ranting against an absolutist straw-man of your own creation.

What I DID say is that:

1. Type-checked exceptions are just as valuable as type-checked returns.

2. The "problems" you listed with checked-exceptions are baseless: Each of them is identical to an architectural decision or task you already regularly complete for happy-path return-types.

3. The fundamental driver of complaints is that people don't like to spend time on the unhappy-path, and aren't as rewarded for it either.

Checked exceptions don't work right with streams/functional programming. They prevent abstractions that couldn't possibly be aware of custom checked exceptions. That's why Scala doesn't have them.
Lombok is a useful crutch if you're writing lots of Java code on a day to day basis. However, given how easy it is to use Kotlin alongside Java I would question whether Lombok is the right solution to the problem.

Kotlin has data classes which auto generate sensible 'toString' and 'hashCode' methods which is a massive time saver.

Really, if you are considering introducing Kotlin to a Java project I'd recommend you just take the plunge. It's pretty much just a minor change to your pom file (if you're using Maven). Kotlin has Java interop as a primary feature and is flawless from my experience.

> auto generate sensible [...] 'hashCode' methods

Unless you're using arrays. They went with "predictable" over "sensible" here.

> is flawless from my experience

As a heavy Kotlin user, I'd say it's far from flawless (not sure if you mean interop or the entirety of the language, but applies to both). I'm often creating and starring YouTrack issues. But yes, it's still a highly recommended alternative to Java.

As someone considering using Kotlin for my next project, your comment is interesting. Care to share what flaws you've uncovered?
Assuming it's for the JVM, you should definitely choose Kotlin for your next project. Most of the flaws I find are for advanced usage (e.g. interop, coroutines, etc) or tooling (e.g. gradle issues, dokka) or just general advanced language use. I don't keep a running list of issues (I made one back in the day [0] but not all of it is still applicable), but nothing would keep me away from the language. It made a lot of right decisions.

0 - https://gist.github.com/cretz/2a49514b18914ef09b7c518db6db11...

Jetbrains has a very good 4 weeks free course on Coursera https://www.coursera.org/learn/kotlin-for-java-developers. We’ve beginning to introduce Kotlin in our code based, and also begin to use Kotlin Dsl on our new Gradle projects; for the moment, the experience is excellent. Interop with Java is really good and the support from IntelliJ is also fine.
I agree, it's as easy to tap into your build tool to build kotlin/groovy/scala alongside with java, as it is to tap into buildtool to process the annotations.
Yeah, Kotlin pretty much killed Lombok.
I see Kotlin as a great choice for these who don't want Java verbosity but can't really get used to read Scala.
I can read Scala fine but I'd like fast compilation and good IDE support.
The IDE support is pretty good these days, as least in IntelliJ.
Not sure why you were downvoted. IntelliJ’s Scala plugin is top-notch in my experience.
True, nor do I understand your downvote.
Apart the fact that I would disagree on intellij support, what about the other ides? Eclipse? Visual studio (not code)?
Scala support in IntelliJ is ok, but still not great. There are some Akka constructs IntelliJ underlines in red for years and JetBrains doesn't fix it. I've never seen something like that with Kotlin.

The hard truth is that Scala was not designed with tool support in mind and it probably never will be. For example hierarchical implicit conversions, while powerful, are terrible for tooling.

> Scala was not designed with tool support in mind and it probably never will be

You should pay attention to what's going on in the ecosystem. Dotty comes with LSP support. The Scala Center's main focus is tooling. JetBrains has been pretty committed and cooperating as of late. And several projects are building serious alternatives to SBT.

Agree. In fact, we have a rule in our team that if a feature is not supported by IntelliJ, then it's probably too arcane and should not be used.
Scala syntax is decent tbh, unless you decide to go overboard with operator overloading.

Most libraries — SBT is one example — have shifted away from the use of excessive overloading.

That time when my boss started using "implicit" on everything. We were even using a new "Functional Relational Mapping" framework from Lightbend/Typesafe called Slick which was a mess.
Tend to agree. But if you work in a team, there will be someone who just loves implicits and overloading and you won't be able to change his mind. Strong code style policies help, but they are really rare in real processes.
Your team can make a decision as a group to not allow defining operators in the team's codebase. As you mentioned you can then enforce that with scalastyle (set FieldNamesChecker and MethodNamesChecker regexes to ^[a-z][A-Za-z0-9_]*$). Is that not enough to handle the one person who loves overloading?
The same can be said about Scala and Groovy. (Groovy brings in those generators via an annotation)
Of those three (Kotlin, Scala, Groovy), Kotlin is by far the most pragmatic. Scala is nice, more powerful, but has a high cost when it comes to learning curve, complexity, and the library ecosystem. I'm not sure of the state of Groovy at this point after it got transferred to the apache foundation. Kotlin on the other hand strikes a nice balance in many areas.
Kotlin got a major push on Android, thanks to a stagnant Java 8 support.

With ChromeOS and Fuchsia on the game as well, it remains to be seen who will win Google's internal OS wars.

So far Android seems to be the one taking them all, though.

Outside Android I don't really see a major benefit switching whole teams away from Java, Eclipse/Netbeans and rewriting/wrapping libraries to be idiomatic Kotlin and such.

How do you define pragmatic? Apache Groovy is still under active development. We've actually started using it more for green-field development where I work.
What exactly does "Groovy" offer that another JVM based language doesn't? Am genuinely interested.
Not the parent, but I prefer Groovy over other JVM languages because:

- supports both dynamic and static typing - it works just as well for bash level scripting as it does for full application development. For example, you can use it exactly like an interpreted language (no compilation needed).

- seamless Java integration. I know many JVM languages say that, but Groovy actually makes it one of its core features. It has a lower impedance mismatch than any other JVM language I've tried (and I try them all).

- partly because of the dynamic typing, it is able to support the most concise, powerful options in its syntax of any of the JVM languages. So you get more done with fewer, simpler lines of code

- because of the static typing and low impedance mismatch with Java, it can achieve very high performance. I was not able to get Scala to perform as well, for example, because of type conversions needed to invoke underlying Java library calls (unless you abandon idiomatic Scala).

The seamless integration is amazing. For a lot of existing Java code, it's as simple as renaming the file to .groovy. In addition to your list, some of our most used features are

- The amazing SQL library - HTML builders (there are XML builders too) - Literal collections (lists and maps)

> For a lot of existing Java code, it's as simple as renaming the file to .groovy

This causes the program to run in dynamically-typed mode, much sloooower, so why do it? You'd have to add @Static annotations everywhere to get anything approaching Java-like speeds. One of the Apache Groovy project managers has long since wanted to make Groovy statically compiled by default, but that's never going to happen because they're running an unofficial go-as-slow-as-possible in the official upcoming upgrade to Groovy 3.

Kotlin has not flawless integration and some attention is required when writing libraries for Java consumption.

Additionally the JVM wasn't enough for JetBrains and code written for multi-platform has slightly different semantics (see Kotlin/Native and immutable data).

Kotlin is a good alternative on Android due to a frozen Java 8.

Against Java 12 not so much.

It is very hard to displace platform languages when others just feel like guests.

I would argue that Kotlin brings massive benefits over Java 12:

  * Excellent null safety enforced by the type system
  * Data classes reduce boilerplate and bug sources
  * Extension methods
  * Declaration site variance removes a lot of the ? extends T
  * Everything being an expression (if, switch/when) is powerful 
  * Smart casts are useful, especially when combined with subtypes and when (switch)
  * Removal of checked exceptions
  * Inline functions, a good way to work around runtime type erasure
  * Properties, no more getX or setX
None of these are the killer reason to go over to Kotlin or another JVM language, but all combined they make building software much more efficient. Java could get all of these, but they've tried to increase feature delivery speed multiple times by adjusting release schedule and failed so far.
I give it 5 years time for Kotlin to be having beers with Scala, Clojure, Groovy, Beanshell, Ceylon, XTend in terms of global JVM developer market share (I do not count Android as part of JVM market).

https://trends.google.com/trends/explore?geo=US&q=java,kotli...

Specially since JetBrains decided that they not only want to go against Java, they are also positioning Kotlin agoainst JavaScript, TypeScript, WebAssembly languages, C, C++, Rust, .... Ergo choosing multiple fronts to focus on.

Which already has the consequence that Kotlin/Native code does not fully follow the same semantics.

Also regardless of what people in HN think, Eclipse still rules the enterprise and the Kotlin plugin is a bit meh playing catchup with latest features. Most IT departments won't sign off InteliJ licenses just because some devs think Kotlin is cool.

It is not language feature bullet points that make an eco-system, specially for those that don't own platforms.

Kotlin is great. But go and try to persuade your company to bless another language for production.

It may be easy when your engineering org is 5 persons. It's much harder, genuinely harder, when it's 250 engineers, and an hour of downtime costs hundreds of thousands. Adoption of another language, even clearly superior and interoperable, becomes a large and costly undertaking.

So "just use Kotlin" is not always possible, and I'm thankful to Lombok's authors for their work.

I am not parent commenter, but your comment really struct the chord here. I failed to convince 2 people (my mgr and team-lead) for our relatively-green-field project 2 years ago to use Kotlin... Thing is - same people killed an idea to use Lombok as well. So it is an uphill crawl no matter how "big" is the change - it is how comfortable people are with what you are proposing, and where they (and you) are in a command (aka responsibility-and-headache-if-something-new-goes-wrong) chain.
You’d have to spending 192M a year on developers for 1 hour of downtime to cost hundreds of thousands.
I'm interested in the downsides of using Lombok, since the article seems to only focus on its positives and makes it seem like I should download it and start using it right now.

Is there anyone here with Lombok experience that wants to share any issues they've run into while using it? All I can think of right now is the fact that the source code isn't compatible with Java.

The downside is that you'll need to install it into your IDE. And again when you upgrade it. I guess the version needs to loosely match the one your projects use too. That might not sound like a lot but some colleagues hate that overhead.

I love it though, think I've been using it for at least 8 years now in just about every Java project.

> The downside is that you'll need to install it into your IDE. And again when you upgrade it. I guess the version needs to loosely match the one your projects use too.

If you work on a lot of different projects with different versions of Lombok, do you have to have multiple versions of the IDE plugin installed / is that even possible?

Lombok doesn't really change that much/often. I've been using it for a decade on projects of varying ages and never noticed a problem.
Yep, also never had an issue with it in practice.
The @Builder annotation has some odd behavior if you want to add default values (it just hardcodes them and you cannot override them!) or extra builder methods.

Actually, anything involving default values seems to be very brittle and difficult to work with -- especially when deserializing objects from JSON.

The @Wither annotation results in methods that do not always make a copy, and sometimes use == instead of .equals when comparing class members. If you call one of the Wither methods and assume you have a copy of the original object, and then you modify that "copy", you might have just created a very subtle bug.

If you are using IntelliJ you can use the Refactor > Delombok menu option to show you the code that Lombok generates. I've been told that does not actually invoke the same code that the annotation processor invokes at compile time, so the results of Delombok might be misleading.

IIRC @Builder.Default only makes them immutable if you also specify that the variable is final.
Yeah, but what if I want to build an object with @Value on it, which makes all variables final, and I want to specify a default value to be used only if the user does not provide one to the builder? That's not possible.

A builder is just a fancy alternative to a constructor. It is possible to set the value of a final field in a constructor, and it is also possible to have another constructor in the same object that sets a default value for that field. I expect the same flexibility from a builder.

For example:

  public class Person {
      private final String name;

      public Person(String name) {
          this.name = name;
      }

      public Person() {
          this("Bob");
      }
  }
Now you can get immutable Person objects:

  Person p1 = new Person();
  Person p2 = new Person("some other name");

But if you do this with Lombok:

  @Value
  @Builder
  public class Person {
      @Builder.Default("Bob")
      private String name;
  }
You cannot do this:

  Person p = Person.builder().name("Some other name").build();
The name is stuck at the Builder.Default value. In that sense it is not a default, rather it is the only possible value. A default is supposed to be something that can be overridden.

I could use @Data instead of @Value but then the resulting Person objects would be mutable which I don't want.

Can't you just use @Data and @Setter(AccessLevel.NONE)? That's effectively the same as using final but without the keyword.
I used it extensively while writing Java code at Airbnb. (It came included with Dropwizard, which we used for writing services.)

Some people are uncomfortable with the extensive bytecode manipulation that it does. While Lombok provides annotations, it is not your normal annotation processor. From http://notatube.blogspot.com/2010/11/project-lombok-trick-ex...:

> Project Lombok hooks itself into the compilation process as an annotation processor. But Lombok is not your normal annotation processor... The trick is that Lombok modifies the AST. It turns out that changes made to the AST in the Annotation Processing phase will be visible to the Analyse and Generate phase. Thus, changing the AST will change the generated class file.

That said, we never encountered any Lombok-related problems when running services in the cloud or locally. And the Lombok plugin for IntelliJ is good enough in that the auto-complete will "see" the Lombok-modified version of the file. For example, using the @Value annotation creates an immutable value type, which among other things a) makes every field private and final, and b) generates a getter method for each now-private field. With the Lombok plugin, IntelliJ auto-complete will a) not auto-complete the composed fields which are now private, and b) auto-complete the generated getter methods.

I highly recommend looking past the voodoo bytecode manipulation and using Lombok. The @Value annotation alone is worth the price of admission and made me a more productive programmer.

Lombok doesn't come included in Dropwizard.
Oops! My mistake – you are absolutely correct. Lombok was just so pervasively used in our services, I assumed it came included as part of the Dropwizard toolbox.
Just recently removed Lombok from one of our projects. 1. To use it you have to use plugin for each IDE. If project not frequently used, spending time for each developer here doesn't make any sense. 2. Another reason was, person who used Lombok abused all OOP principles. Project became a book of anti-patterns.
Idiots can do all sorts of terrible things with any tool given enough time and effort. That's not really Lombok's fault.
What were some of the OOP principles that were misused?
Yeah, I use it at a financial services client. A previous architect chose it because it was a pet project of his (he contributed to Delombok).

The premise is fine...I have no problem with it. But the default generation of @EqualsAndHashcode literally pulls in the WORLD to generate the output.

The real world scenario we had was this. Lots of POJOs were created, many were simply but a non-trivial number were NOT. Those POJOs could have dozens and dozens of fields. And if you have a key abstraction with say, 86 fields, things get interesting.

Suppose you don't use @EqualsAndHashCode on one of these POJOS with lots of fields, ALL 86 fields are included in the default equals and hashCode methods. They didn't realize this, or didn't care, and as a result, had some serious performance issues because trying to run hashCode on insert to a map when you're hashing 86 fields together might actually take some time inserting 100,000 records... ugh

So in short, it's OK and useful, but you have to understand the side effects of everything to know if it's the right thing for you.

SIDE NOTE: A POJO with 86 fields can be common in financial services when you are representing various kinds of financial trades where gazillions of things are tracked on them...interest rates of note, ratings, security characteristics, etc. That in and of itself isn't necessarily poor design, although these choices predated me at this company.

I have a /really/ hard time believing you wouldn't factor out a couple of POJO's from those properties that belong together and include them to compose your classes. Sounds more like sloppy data modeling.
You get a more complex (and slower) build. Poorer editor support. It may be hard to upgrade Java version (ie. going from 8 to 11). More string for developers to hang in. I wouldn't use it.
I've heard it makes Kotlin integration a pain also if you are ever planning on that. If your Kotlin code depends on Java lombok classes, the compiler fails. Most people probably don't have this concern, but IMHO a mixed Kotlin-Java project is better than any of the current 'data class' libraries available for Java (Lombok, Immutables, Joda-Beans, AutoValue) ... ymmv, and I'm eager to see what comes of the "data classes in java proposal".

https://cr.openjdk.java.net/~briangoetz/amber/datum.html

One of the issues is that it's tied quite heavily to the Java compiler, so you may need to wait until Lombok supports a new Java version before you can use it.

Also as it is compiler magic, it can be a bit confusing to developers who haven't used it before.

The only downside I've come across was test coverage tools like jacoco can have problems with the auto-generated code (complaining about your tests not covering enough branches of an auto-generated hashCode() implementation, for example). However it seems new-ish versions of jacoco seem to have built support for it.
I dislike Lombok. One reason I dislike it is that it makes IDE navigation, even with a required Lombok plugin, more difficult. A common task in a Java IDE is finding usages. It's harder with Lombok. Generally it's just more friction than plain Java code.

I also don't like that the generated code is not checked in or visible. It makes code review harder. In theory you can have magic change to all Lombok classes just by upgrading the library.

Also it doesn't pass the cost/benefit test for me. Adding Lombok adds complexity to your code, build system, and IDE. What do you get? Slightly shorter classes? Less characters? Most of this code can be generated by the IDE.

I'll give Lombok one win. It will keep equals and hashcode up to date if you add properties. That's a pretty common error.

As others have said, Kotlin is the best alternative. But even without Kotlin I skip Lombok happily.

> Most of this code can be generated by the IDE.

If you check in generated code, you have to prevent anyone from editing it (so it doesn't become something you have to start testing and reviewing) and include some kind of summary so nobody wastes time looking at it.

One of the major features of a language is its syntax-- and here, it seems as if the author, instead of simply using another language, is "fixing" Java's verbosity by installing a compiler pass that converts "shorthand" Java into valid Java code. Why wouldn't the author just use another language?
Using Lombok is relatively simple compared to switching to Kotlin or Scala, I think.

A few annotations don’t seem that much more radical than stuff like AspectJ.

Sometimes just fixing the rough edges with syntactic sugar can provide a lot of value. Like, CoffeeScript got pretty far with that strategy.

Honestly, a lot of the best TypeScript features could be described the way you describe it.

My main issue with Java is all the annotation based programming. Some of these are nice and can make the easy case super easy but if you need go even slightly off the easy path you seem to quickly loose all the time gained on the easy path.

i.e.

@GET(url="http://host/users/$userid") public abstract User getUser(String userid)

isn't that much easier than the python requests version but much harder if you need to add a custom dynamic header to the call.

FactoryFactoryStrategyFactoryAgreementChainImplementationProxy
ffsfaci_proxy() is definitely much better.
indeed + a few lines of comments explaining what it means...
>custom dynamic header

Its just: @RequestHeader("Header") added to the param list.

The secret is that there is always another annotation...
How do you add that dynamically?
What do you mean? Like, look for any header? You can just grab them all from the request context.

>@Context HttpHeaders headers

I'm not sure if there's some kind of regex support or anything like that. Looping through the headers seems simple enough.

I actually ran into a useful case for annotations in Java the other day.

Compile time dependency injection (Micronaut framework).

Not something I have a use for but it's clever.

https://micronaut.io/

It's not exactly a new thing though, it's always been possible in a variety of ways, from build steps to pre-processor macros. Annotations mostly just do what macros or code generators have always done.
Yep. I love data classes for the simplicity and utility but pretty soon there are more annotations than java code. In Java I prefer Immutables over Lombok but once you add Jackson annotations, customize the Immutables generated code (@Value.Immutable, @Value.Parameter, etc), add some javax.validation (@NotNull, @NotEmpty) it gets very noisy very quickly. And yet it is still better than writing mutable bean classes by hand IMHO.

Switching to / mixing in Kotlin is worth it just for the data classes at this point (and you get some type-level nullability and `val` instead of `final var` to boot).

https://immutables.github.io/

I'd also prefer immutables.org over lombok, because it cleverly gets by without changing the way sourcecode is turned into bytecode. All it ever does is generate additional class files that add the boring stuff. If lombok says "we can have all this awesome stuff if we break the rules a bit", immutables.org answers "we can have almost as much of we don't".
I would be nice for any language that supported annotations(TypeScript, C#, python, etc) if we could somehow annotate the same code in different files while maintaining a single source of property truth.. A combination of interfaces and class annotations for combining them could work..
I don't know if this is what you mean, but Aspect Oriented Programming was a hot topic for a very short period in the early 2000's, especially in the enterprise Java community. Ultimately it didn't seem to find many use cases and the buzz died down pretty quick.

https://en.wikipedia.org/wiki/AspectJ

The hype wave is gone, but if you ever saw @Secured or @Transactional, those are aspects after all.
Or just register the Jackson Java 8 datatypes module.

Then you can create a standard immutable class with a constructior, and Jackson's object mapper can then serialize and deserialize.

The only catch is that you must add -parameters to your javac args.

This is what I do. Can attest that it keeps things very clean and lean.
> My main issue with Java is all the annotation based programming.

That seems like an easy problem to solve. Don't use annotations.

Snark aside, it can be a bit of a tricky balancing act. When there are more annotations than actual code, that's a code smell in my book and something needs to change. Separating concerns usually does the trick. I'm heavy on code generation, so anything that's boring enough to benefit from oodles of annotations will probably end up getting generated based on a 2 line config file or something.

why differentiate annotation from non-annotation code? they are both code, and they are both lines you have to maintain/debug.
That's a fair point. IMO it's easy to mix a whole array of concerns with annotations, much more so than what you'd likely do in 'normal' code. Some annotations are really compiler instructions ("don't print these warnings"), some influece the build process, some are AOP entry points, some add runtime behaviour etc, some are security related etc. I guess I like things to be simple.
while I agree with your point, you should have seen the garbage that annotations replaced...
Java the language is cool. What the enterprise has done with it is not cool. Writing apps with layer upon layer upon layer upon layer of abstraction is ... self defeating. Then they'll holler, we need to rewrite it! All in the name of finding that one true architecture that can handle any business CR. Blech.
How do you feel about Spring? I'm a fan of Java itself as well, but find myself conflicted whenever I use Spring/Boot. It's very nice when it works. But when the magic formula isn't recited correctly....you get frustrated and wind up visiting HN to forget about how unpleasant it is.

ahem

Spring is obsessed with using anything but actual Java code to express business logic in Java programs.

XML bean configuration files. Annotations. Implicit code generation. Property and YAML files. Looking for files and resources in "default" locations. Executing code at runtime as the result of adding a dependency, without it ever being explicitly called anywhere.

The result is, you can never really predict what the hell your program is going to do when its running in production. Certainly not by carefully reading and inspecting the Java code you write.

I think that it brought a good competition against JEE (back when it was still J2EE), however I would still just use JEE for Java Web projects.
Just curious -- why?
Short answer, experience and employers.

My experience doing Web development in Java started with joining a team that was ramping up a project on WebSphere 5.1, alongside a migration from BEA WebLogic.

I went through a couple of WebSphere, JBoss and TomcatEE versions up to JEE 6, was the technical lead for an in-house JSF framework built on top of RichFaces, and on very last JEE project used PrimeFaces.

All the employers that I worked for, mostly used JEE stacks for their Java projects. Spring was the exception, rather than the norm.

Additionally, even if a bit slowly, many of the Spring improvements ended up in JEE and Spring also supports the JSR related to JEE anyway.

Nowadays I live in the .NET world, and the Java teams next door are happily delivering JEE based solutions.

So if I was suddenly asked to architect a Java based solution, naturally it would be JEE based.

Java made some fundamental design decisions at its inception which look like fairly bad ones from today's perspective. It also introduced flawed versions of features like generics later. It also has some pretty poorly designed core libraries. What about it do you consider cool? I find it hard to think of anything I'd want to take from Java if designing a new language today. Even its bad design decisions have mostly been done better by other later languages like C#.
Genuinely curious, which language do you find is better designed?
Most of them to be honest :)

Some languages do at least one thing particularly well, setting the standard in that regard. Some introduce or popularize new concepts that slowly see adoption elsewhere. Several make you think about programming in a different way. Some just have an overall elegance, unity, consistency or simplicity of design that make the whole greater than the sum of the parts.

Languages that check at least one of those boxes for me include C, C++, Lisp, F#, Haskell, Lua, Python, Elm, APL, SQL, ISPC and Rust (I don't claim to be an expert in all or even most of those languages). Other languages that seem interesting / different but I know almost nothing about include Erlang, Clojure, Smalltalk, Prolog, Forth, Coq, Idris and Kotlin. By contrast I've not really seen anything in Java that checks any of those boxes and isn't done better elsewhere (admittedly sometimes by languages that came later like C#).

Java popularised GC. GC wasn't taken seriously as an option for high performance code before Java, and it altered the mindset of the majority of programmers globally.

Java's biggest mistake IMO is checked exceptions.

Java is more notable for the things it left out than what it added. It was designed for success and achieved more than most of the languages you list.

I don't love Java. However there are many cases in which it's still the best option.

I'd argue that GC still isn't taken seriously as an option for high performance code by people who actually write high performance code and Java is not the language of choice when performance is a primary goal. The lack of value types hurts it there even compared to other similar GC languages like C#.
Tell that to the high-frequency-trader crowd, they almost use java exclusivly, because even 1ms equals a few thousands to them, correctness is apparently worth it for them.
My understanding is that C++ is commonly used in that domain due to the performance requirements, though I don't know how common Java is relatively. C++ is used enough that there is a performance focused Study Group SG14 on the standards committee that explicitly includes trading as a use case: https://groups.google.com/a/isocpp.org/forum/m/#!forum/sg14

I'm from the games programming world where C++ still dominates for performance reasons. Where GC is widely used with C# in Unity, Unity are building their own C# compiler without GC for performance critical code (the Burst compiler) and GC is a major cause of performance issues in existing Unity games.

C++ and another non GC language (Fortran) are also the most used languages for HPC ( High Performance Computing) in scientific computing.

C and C++ also dominate in performance sensitive and resource constrained embedded programming.

It's amusing to see someone defend absence of GC by talking about how niche that's become ;)
I don't really know what you mean. I took issue with the claim that Java demonstrated that GC was suitable for high performance code and pointed out that even though GC is more common today than it was when Java came out that it is still avoided in applications that prioritize performance. Maybe those are 'niche' but some of them are rather large niches and there are several of them.

I personally don't find GC very useful nor do I think it's a particularly good idea and it's great to see new languages like Rust taking new approaches to memory management but I recognize that it's now the default in the majority of applications that don't prioritize performance or deal with severe resource constraints and that many programmers find it valuable. There are actually several languages I like that rely on a GC, Java is just not one of them.

how common was it for languages to ship with robust IO, collections, and threading libraries? It might be that a lot of the good ideas in Java have become so commonplace you don't recognize them as "from Java".

(Of course, there is very little new under the sun so I'm not claiming Java was the first to do anything in particular)

Java not only adopted a bad model of concurrency / parallelism, it enshrined it in the language in the form of language level synchronization / locking. True, it wasn't as widely appreciated at the time that it was a bad model but there were better alternatives out there even back then. The focus on a well defined memory model in the presence of parallelism is one thing Java can take some credit for but it is to some degree a consequence of the sub-optimal threading/locking model they adopted at the language level.

The original collections library in Java was poor due to lack of generics. Again, there were better models out there and some of them were even in fairly wide use at the time (C++). Java is only recently catching up to better models for collections with streams that many other languages have been doing in some form for a long time.

I don't have much of an opinion on Java IO or how it compared to contemporary alternatives but most languages have pretty robust support for basic IO in their standard libraries. What about Java do you think was particularly good for the time?

One thing that I think Java deserves some credit for popularizing is reflection, although the Java model of dynamic reflection isn't what I'd pick if designing a language from scratch today.

>What the enterprise has done with it is not cool. Writing apps with layer upon layer upon layer upon layer of abstraction is ... self defeating. Then they'll holler, we need to rewrite it! All in the name of finding that one true architecture that can handle any business CR. Blech.

Those decisions were less to do with personal freedom or program simplicity, and more to do with the realities of large scale enterprise development. You end up having to adopt patterns that make individual programmers replaceable, and allow for a standardized pool of candidates with experience in $X framework. The reality is that most companies building Java software just need to be able to hire an average Java programmer to show up every day and manage their huge tangle of business logic.

> Java the language is cool. What the enterprise has done with it is not cool

This is a re-occuring pattern.

I have seen it happen with C, C++, TP, Clipper, Java, JavaScript, C#, VB, ...

The enterprise environment is quite "interesting", specially when politcs get intermixed with technology.

Somewhat serious: LombokScript please.

To quote @Retra in this thread: "I feel it is important for writing code to be fun". IMO, programming is fun when you are solving "business" problems, not writing boilerplate or handling a language's myriad edge cases.

I kind of agree, but I also find it curiously self-indulgent that as a profession software engineers feel entitled to discard technologies and methodologies because they are "not fun". Can you imagine civil engineers or electrical engineers saying that? They might enjoy working with certain technologies but would consider it highly unprofessional to demand that their projects be "fun" over other considerations.

NB: Groovy might be the closest thing to "LombokScript"

(comment deleted)
Have you ever noticed how these "cool again" posts always seem to come from companies with billions of dollars that struggle to make their sites work under relatively low load?

Five times in six their grocery search - literally the core of their product - 502s on me. In the remaining 1 of 6, if I just keep repeating, I get random subsets of the data I should actually be getting. It's amazing to me that I haven't seen a fail whale yet.

I refuse to believe that Instacart has a large practical server load.

These are not the people we should be listening to.

> Although Java is one of the most popular JVM languages

Is it not the most popular JVM language?

Is it not the definition of a JVM language?
Java is by far the most popular JVM language - probably by a factor of at least 10.
10 is a low estimate. I'd say it's more like 20, with Scala dominating the rest.
I think Java is just 99% of JVM languages while other ones together take whopping 1% share of JVM languages.
We initially used lombok to decrease the pain of writing in Java but eventually moved to Kotlin and are sooo much happier.
Kotlin is great and very easy to use if you already know Java.
What's the point of this part of the generated Java code? (I split it out onto multiple lines for legibility)

    favoriteFoods = new java.util.LinkedHashSet<String>(
      this.favoriteFoods.size() < 1073741824
        ? 1 + this.favoriteFoods.size() + (this.favoriteFoods.size() - 3) / 3
        : Integer.MAX_VALUE
    );
    favoriteFoods.addAll(this.favoriteFoods);
Why not just?

    this.favoriteFoods = new java.util.LinkedHashSet<>(this.favoriteFoods);
1073741824 is maximum (signed) integer size divided by 2. This is trying to optimize for the initial size of the set so that it doesn't have to allocate too many elements, since iirc Java's hashsets are backed by a hashmap implementation which has to be sized appropriately.
I understand what it’s doing. I’m curious why it’s necessary to do it explicitly. Wouldn’t the optimal sizing be the default behavior when passing in the collection that will populate the set?
At this point, it's less disruption to a Java project to start writing it in Kotlin than adding Lombok to it.
i also wanna give a shoutout to https://immutables.github.io/ over Lombok. No AST magic, and even optimized Jackson/GSON code.
This, a million times.

In my professional bubble Lombok is completely gone. I'll go as far and say that modern Java does not use Lombok. Smaller, focused libraries like Immutables or AutoValue, solve the problem of boilerplate for data classes.

Lombok tries to do too much across many concerns, in a fairly opaque way, and makes the code and tooling around it more magic than it needs to be.

Skip Lombok. Modern Java is better off without it...

Even cooler is using a JVM language that builds many of these features in (my favorite is Groovy). Java will always be weighed down by the (necessary) baggage of backwards compatibility. I think companies still confining themselves to strictly using Java and nothing else are doing themselves a disservice when there are JVM languages that offer almost perfect compatibility at the bytecode level.
I spent considerable time playing with Java and various usability extensions, but if I could go back I would have ditched Java much sooner. ML based languages really are an order of magnitude productivity boost.
"ML based languages really are an order of magnitude productivity boost."

Is this still true when you consider available libraries, performance, concurrency, build and dependency tools, development environments, and deployment options?

I'd say it depends on which ML derivative you're talking about.

I've found F# to be pretty good in all of the areas you mentioned. Although not enough to literally provide an order or magnitude boost like the post you were replying to suggested.

I think people use "order of magnitude" a little too freely. Developing with Java (and to a lesser extent, C#) feels slower than with F#, but not 10 times slower.

I was not exaggerating, although you would not see the same improvements for all applications. The big win for me is do notation (or computation expressions). I hate to think how I would even write complex async code without it. If I was only writing something linear then Java wouldnt be so bad.
I use the elements of programming from SICP[1] as a litmus test for this kind of frameworks.

The main issue with these annotation hacks is that they fail in the area of “means of combination” and “means of abstraction”.

For example if you have: @X @Y class Something{}

You cannot tell if X & Y can be combined (or how it’s combined), or you cannot easily create an abstraction Z with X and Y (you may be able to do that but it requires framework support and usually is painfully impractical)

It seems to be a small issue compared to the added value, but to me is not. As the code base grows the maintenance and debugging issues grow too... in the end probably is better to use Kotlin or Scala.

[1]: https://mitpress.mit.edu/sites/default/files/sicp/full-text/...

Thanks for that!

Makes clear and explicit the queasy feeling I get from over reliance on Java annotations.

Lisp macros, for example, fare much better in the "means of combination" and "means of abstraction" tests, I think.

I agree, but it's helpful that the things Lombok abstracts away are very idiomatic and straightforward. I would never want to use it to take on anything more complicated.
Annotations have always come across as a crutch. It’s noisy at best and unsightly in large concentrations.
IMO just move to Scala: the change in thinking from mutable, stateful OO to immutable, pure FP (w/ some side effects mindfully mixed-in) is indeed steep. However, accepting this paradigm shift yields unbelievable dividends in terms of quickly writing correct programs that solve complex problems.
Maybe you just nailed it on the head for me what I've been trying to state for a while in regards to FP. Expressing complex things like algorithms in functional programming can be beautiful and also incredible in how it adjusts your thinking. Fact of the matter is, I don't solve many complex problems like this. It makes doing tutorials and books fun but when i need to get work i stick to my usual guns
I use Lombok all the time. Yes it has some awful bits but generally it's positives outweigh it's negatives.
Is there any other language with such thing like lombok? How would one explain why "lombok" is necessary at all? Specially to a newcomer.

With efficient/safer languages like Golang and Rust getting track, I don't see a reason into trying to keep Java around.

Reading this just reminds me of all the terrible things about Java. The lack of reasonable default string representations, comparison and hashing methods, etc. are all glaring mistakes in the language design that have wasted huge amounts of time for millions of programmers. It's as though Java programmers are so deep in the grips of Stockholm Syndrome that ordinary, sensible behaviour seems amazing and "cool".

This is similar to the situation with design patterns. When design patterns came along, Java folks talked about them as though they were a wonderful new invention. But they only exist because Java is so clumsy that it needed crutches to do things that had been easy and natural in other languages for years; someone merely came along and gave the crutches names.

You either like strongly typed object oriented langs or you don't. If you do, then a bit of verbosity is really nothing. If there are actual lang improvements beyond saving me a few characters, I'll take them.
The difference is more in a "batteries included" sense - Python values developer productivity and gives you what you need, similarly Haskell which is on the opposite end of a Dynamic vs Static spectrum, while Java was traditionally more oriented towards stability and predictability and partly attached to the old-school cool of potato programming.
> You either like strongly typed object oriented langs or you don't.

Sorry, but this argument doesn't pass muster anymore, when there are abundant examples of strongly typed object oriented langs which don't exhibit these flaws. C# avoids getter and setter boilerplate with `get` and `set`. Kotlin and Swift, unlike Java, actually are strongly typed, because objects aren't implicitly nullable. And the many conveniences afforded by modern language features add up to improved productivity, provided the programmer using the language doesn't rigidly refuse to learn to use them.

A language being strongly typed does not require verbosity.

See, for example, F#, Scala, or Haskell.

No, it's not that.

It's about having non-nullables. It's about having value types. It's about controlling mutability. It's about the evils of (especially mutable) statics and life-before main. It's about having closures (at least recent Javas have them!)

None of this goes against strong typing or object orientation. Java just is... not so great. Of course, everything about this is hindsight: Java was also a revolution: generics (slightly flawed and bolted afterwards, but anyways), garbage collection, memory safety, JIT in an usable package.

Java has shown it's age and some people are aware of it.

I like strongly typed object oriented languages.

And that's why I just can't love Java these days. Java was great for 20 years ago, but, by modern standards, it's sub-par in both departments.

I don't think that any of Java's warts are related to liking strongly-typed object oriented languages. I think the standard library is Java's biggest problem (read through the code that's been around since the 1.0 days, it's AMAZING in the way that a car crash is amazing). The community has done a good job building a better standard library (back when I did Java it was Guava, maybe that's not the state of the art anymore), and that helped a lot.

All in all, I am not a fan of OO anymore. I like the computer science principles behind it, but practical programs never use them. They always get Liskov Substitution backwards. People always use subclasses for cases like "give me the superclass, but with these conditions" which is the exact opposite of what you're "supposed" to do. Problem is making your subclass less restrictive is largely useless, what people really want is copy-paste without having to maintain two copies of the code. So there is some mismatch to what programmers and computer scientists call classes, and the result is that you get a mess. I'm not sure that's Java's fault or its largest problem, but it isn't helping.

All these words for saying: I think class inheritance is a bad idea. Lots of folks agree. Rust might be your thing!
Which design patterns are you thinking of and which languages had them? The original gang of four Design Patterns book is filled with C++ examples, most likely because almost nobody was using Java in 1995 outside of a few people at Sun.

More generally comparison and hashing methods seem like small potatoes compared to locking primitives, portability, and a large, well-documented class library that have made Java ideal for a very large fraction of business applications. There are many things to criticize in Java but the points you list don't seem to be especially important compared, say, to the relatively steep learning curve/complexity vs languages like modern Javascript.

C++ circa 1995 was a lot more like Java with manual memory management, really. Templates were a rare sight, STL was not yet a thing, and it was still considered an impure OO language rather than multi-paradigm.
STL wasn't a thing, but MFC, OWL and ATL surely.
These were also closer to Java libraries of late 90s in design. Even ATL mostly used templates to optimize (using the curiously recurring template pattern, usually).

Qt was also born in that era, and look at how strikingly similar its standard library is to Java's in many cases, and how unlike STL and Boost.

The way you state it feels like it was C++ that copied those designs, when it was the other way around.

I cannot speak for Boost, but STL is also not free of OOP as such, iostreams, data-structures, allocators.

ATL was all about using templates, as optimization or not, one is on template land when using ATL, it is even part of the name.

I'm not saying Java doesn't have other strengths. It certainly does! It also has stupid weaknesses.

Many of the popular design patterns are simply lambda. If you have first-class functions, they are trivial. But Java doesn't have first-class functions; instead you have to write a giant thing that looks like a class, and because that feels like a lot of work, it seems to deserve a fancy name like Observer or Strategy. But it's all fabricated work; the only reason you have to do it is that the language has this stupid weakness.

It's as though you were working in a language that didn't have lists. And one day you realize, hey, it's useful to be able to manipulate variable-length collections of things. So you look at a problem and say "Aha, this is exactly the place that we should use a Collection design pattern" and you go write the entire implementation of lists from scratch and feel satisfied that you did a good day's work. You tell all the programmers you know that there is this great design pattern called a "Collection" that they should all start using. You write books about it. You give workshops. You get really good at writing list implementations, because every time you need one you implement a new list from scratch.

When you tell a Python programmer this, she looks at you like you have lost your mind, writes a function that takes a list, solves the problem in 5 minutes, and gets on with her day.

I am sorry to disagree but if you think that the iterator or visitor pattern or the state pattern or abstract factories are only bound to Java than you have a very low skills/understanding of those patterns and you really don't understand their importance and/or applicability.
The majority of design patterns only exist because of the limitations of single dispatch. Java is far from the only single dispatch object system, but his criticism is still valid.
I think it's a cultural thing. Other languages don't name things that seem obvious, like iterators or filters. Consider something like filepath.Walk in go; you give it a function that gets called for every file and directory in the root of your choosing. They don't call it a visitor, though, they call it a "walk function". It's just as clear, it's just as much of a design pattern, but they don't make a big deal about it.

Meanwhile, I'm sure in Java that you have to implement some class that is the visitor, which means you have to create a new file, and probably install some VisitorUtils for making file visitors easier to implement, ... that is where it's gone off the rails. (But I am perfectly willing to believe that the exact same API exists somewhere in Java too, and most people browsing directory trees don't have 13 classes to print the names of the files in a directory. But at the same time, I feel like there are pressures pushing you in that direction. That's where the complaints come from.)

> Consider something like filepath.Walk in go; you give it a function that gets called for every file and directory in the root of your choosing. They don't call it a visitor, though, they call it a "walk function".

The ironic thing is that golang doesn't have an even better construct for this kind of pattern, namely pattern matching.

You sussed that out right! Thanks for explaining perfectly, and more rationally, what I was trying to express.
> all glaring mistakes in the language design that have wasted huge amounts of time for millions of programmers.

i would think javascript is the language with which the mistakes wasted the millions in man hours, not java.

And the DOM, CSS, HTTP and ad-hoc HTML extensions.
Not to mention languages like golang that decided not to learn from previous mistakes (like the billion dollar mistake by including null in the language)
There are plenty of competitors! :)
The remark about design patterns is not accurate. Design patterns did not originate with Java and aren't specific to that language. The classic book "Design Patterns: Elements of Reusable Object-Oriented Software", by the 'Gang of Four', was written in and about C++. It was published in 1994 and predates Java.

"Design Patterns", both as a book and a concept, is about the recurring patterns found in object-oriented software; it is about the problems that lead to those patterns, and the solutions to them. Although the book itself is rather dated, the patterns described in it remain relevant to most modern languages. Some of those patterns include: Builder, Singleton, Proxy, Decorator, Iterator, Visitor, Prototype (c.f. JavaScript prototypes), Observer, Chain of Responsibility, and more.

Many languages employ these patterns in the design of their standard library. E.g. the Iterator pattern shows up in a large number of language libraries (Python, Ruby, JavaScript, Swift, Rust).

If you search for "Design Patterns in X", for programming language X, you can find quite a lot of literature about how to employ them in any language. For example, see "Python 3 Patterns, Recipes and Idioms" [1], which describes how to employ many design patterns in Python, or "Design Patterns in Ruby" [2]. A lot of modern literature for many programming languages relies on these patterns either implicitly or explicitly.

Most of these patterns do not exist because of language weaknesses, although I would agree that this is true for some of them, such as the Visitor Pattern. Use of the Visitor Pattern is often better replaced with pattern matching in programming languages that have it. However, this isn't true for all of the patterns (e.g. Chain of Responsibility, Facade, Proxy); and even the Visitor Pattern remains relevant to many languages, especially dynamic languages that do not support pattern matching.

Many of these structural concepts were not new, in the sense that they did not originate with the book; but the GoF took the time to assign names, characterize them, and describe the trade-offs involved in using them.

[1] https://python-3-patterns-idioms-test.readthedocs.io/en/late... [2] https://bogdanvlviv.com/posts/ruby/patterns/design-patterns-...

"about the recurring patterns found in object-oriented software"

... in statically typed languages of that time (like C++ and to some extent Java). At least some patterns solve problems that are not (that kind of) a problem in, e.g., dynamically typed languages.

GoF was written for C++ and Smalltalk developers.
> When design patterns came along, Java folks talked about them as though they were a wonderful new invention. But they only exist because Java is so clumsy that it needed crutches to do things that had been easy and natural in other languages for years; someone merely came along and gave the crutches names.

Design patterns are a useful way of thinking about any sort of design, not even just in programming. Novels and paintings have them, too, so does your favourite language. There will always emerge from experience particular ways of using your medium, and having names for those patterns can actually be pretty useful for thinking about them and structuring them into a good design (like how SICP talks about knowing a spirit's name gives you power over it). You can't avoid using design patterns; someone just might not have come along and named them yet, or you might not be aware of them.

The GoF book itself actually discusses this in the first chapter a bit, here's a relevant part:

Point of view affects one's interpretation of what is and isn't a pattern. One person's pattern can be another person's primitive building block. For this book we have concentrated on patterns at a certain level of abstraction. [...] Our patterns assume Smalltalk/C++-level language features, and that choice determines what can and cannot be implemented easily. If we assumed procedural languages, we might have included design patterns called "Inheritance," "Encapsulation," and "Polymorphism." Similarly, some of our patterns are supported directly by the less common object-oriented languages. CLOS has multi-methods, for example, which lessen the need for a pattern such as Visitor.

You have to look at it from a time when C++ was the mainstream for large scale projects. Java is certainly a god-sent gift to software salaryman all over the world.
>When design patterns came along, Java folks talked about them as though they were a wonderful new invention.

Your history is wrong. Design patterns became popular with the release of the GoF book, which was written using C++ and was published in 1994 -- before Java was even released.

> This is similar to the situation with design patterns. When design patterns came along, Java folks talked about them as though they were a wonderful new invention.

They are/were a bit over-hyped and sometimes overused inside the Java community, that's true.

> But they only exist because Java is so clumsy that it needed crutches to do things that had been easy and natural in other languages for years;

Sure, some design patterns work around language deficiencies or are that commonly used to justify including them into the language. Java sadly did not do this enough in the past, that's right. But if you look through the list of design patters only a minority fall into this category.

> someone merely came along and gave the crutches names.

That's basically the whole point of design patterns. It's assigning a name to things already widely used to improve communication between developers and to provide learners a point to look up a concept.

Java really felt refreshing versus what C++ world was doing before it came out.

Go dig out a book about Boochs, CORBA, DCOM/MTS,...

Something like this would be handy for the .NET framework.

Off the top of my head; PostSharp (although not free) would get you much of this. Also might be achieveable via Roslyn? And there is another .NET AOP framework whose name eludes me at the moment... EDIT: Fody.

Maybe just use a better language instead?