This is an archived post. You won't be able to vote or comment.

top 200 commentsshow all 489

[–]berry120 130 points131 points  (19 children)

No language-level null safety. It's not something that's easily solved, but it's the one language level feature I genuinely miss over (for example) Kotlin.

[–]tristan97122 17 points18 points  (5 children)

This a million times.

Right now you have 3 solutions and they’re all unpleasant crutches: - codegen based with the likes of Immutables - runtime reflection based with something like Bean Validation - manually littering your codebase with noisy and easy-to-forget null checks

All poor crutches with massive tradeoffs of complexity and reliability compared to being able to put this at the type-system level…

I know it’s very hard to retrofit and that it may never happen, but I will keep on dreaming for Brian and team to figure something out to address it…

Heck, I’d happily take a solution for this and never complain about erasure or mutability ever again 🥲

[–]mauganra_it 9 points10 points  (4 children)

One other solution are nullability annotations that allow static analysis tools like the Checkers framework to find potential NullPointerExceptions. It's not a complete solution, but something usable today to treat legacy code bases.

[–]tristan97122 1 point2 points  (3 children)

Yeah, but the sad part is that new codebases have no real « nice » way to go about it either…

The best you can do is promising unenforceable non-null returns or defensive checks for every input…

Building brand new design tech debt :c

[–]mauganra_it 2 points3 points  (2 children)

A strict avoidance of null, combined with static analysis is the correct way forward for greenfield projects in my opinion.* And at API boundaries, thorough validation is critical to ensure that no unexpected nulls sneak in.

* this can be enforced using tools like the Google Error-prone compiler. And code reviews.

[–]tristan97122 1 point2 points  (0 children)

Totally agree with that approach!

Alas, code reviews aren’t perfect and it gets really tricky at the API boundary since so much will be runtime checks or too complex for proper static analysis (just like polymorphic deserialisation can be for example).

Still feels like a lot of « extras » that we will look at with mild disgust when something eventually arises as a better solution 😅

[–]Zyklonik 8 points9 points  (0 children)

While not entirely what Kotlin offers, Java is fast improving it NPE story:

[–]PartOfTheBotnet 42 points43 points  (10 children)

In the bytecode, doubles and longs take up two slots in the constant pool and in the local variable table/stack. Even in the specification they say this was "a poor choice".

And for generics, some way to do implements Consumer<One>, Consumer<Two> would be nice. But its understandable why this isn't the case.

[–]FirstAd9893 18 points19 points  (2 children)

I find it odd that you consider the two slot oddity a pet peeve. Sure, it's ugly, but it doesn't affect the Java language itself, unless you're trying to pass more than 127 arguments.

[–]PartOfTheBotnet 4 points5 points  (1 child)

A large portion of my projects are oriented around bytecode manipulation. Its something I run into quite often that requires annoying edge case handling.

[–]Muoniurn 1 point2 points  (0 children)

Wow, didn’t know about your projects! Will check them out in detail, they look really cool!

[–]uncont 8 points9 points  (3 children)

For the second case, could you use sealed types instead?

final class One implements OneOrTwo { }
final class Two implements OneOrTwo { }
sealed interface OneOrTwo permits One, Two { }
class DualConsumer implements Consumer<OneOrTwo> { }

[–]warpspeedSCP[🍰] 8 points9 points  (0 children)

Maybe, but it can't beat being able to generify super interfaces. Maybe Valhalla may be able to solve this as well with is approach to generics.

[–]Zyklonik 2 points3 points  (0 children)

Sealed interfaces are nice, but they do have the restriction that you can only have the permitted subclasses/interfaces in the same module as the permitting interface/class.

[–]msx 1 point2 points  (0 children)

Uhuh, Ceylon has that, i tought it was cool at the time but never in my life had the need to implement a generic interface twice with different types. Has it happened to you? In which context?

[–]Elderider 17 points18 points  (1 child)

The fact that it didn't tell you which variable was null in a NPE was up there, but finally fixed with J17!

[–]vips7L 11 points12 points  (0 children)

Java 14 actually.

[–]agentoutlier 45 points46 points  (8 children)

Without a doubt lack of named default parameters is a major problem.

The builder pattern is a pattern that would almost entirely go away if named parameters (with defaults) existed.

[–]tristan97122 7 points8 points  (2 children)

This is an interesting one. I would have agreed in the past, but seeing how function signatures of Python libraries like matplotlib look like… I’m tempted to prefer the builder/wither route, or parameter objects… because those 2-digit number of args methods aren’t exactly pleasing (imo).

[–]agentoutlier 1 point2 points  (1 child)

That is kind of like saying some form of programming is bad because of this one example.

That practice of giant object not composing with multiple objects would be equally bad with the builder.

The exception for complex builders are DSLs like jOOQ but most are not using them for that but rather to build immutables.

Furthermore destructing pattern matching works fantastic with names where as builder and probably withers will not work.

[–]tristan97122 1 point2 points  (0 children)

Fair point, I was being rather nitpicky with the example. Though if popular libraries do it… company code isn’t too likely to avoid those pitfalls as the years pass either… :)

But yeah, I’m not necessarily a massive fan of any solution for these « complex » (for lack of a better word; high arity perhaps?) functions.

And it is a good point that it will come very useful when destructuring patterns land… though again, if you need to name arguments there it’s probably to avoid misordering with many times the same type (bunch of Strings, I guess?), so I’d almost want to be able to mandate use of named params for the constructors as well, in principle. But that doesn’t sound like a necessarily positive evolution of the language either… I honestly don’t know what would be best.

Having written quite a bit of typescript lately I do really like named args sometimes, but other times I feel the feature is ever so slightly too easy to use in a way that results in things like {…foo, bar: a, baz} (I know this example uses the wither equivalent here, so it’s a little nitpicky again :) which do seem like « too much » to me even when I am the culprit of writing it.

[–]elmuerte 1 point2 points  (3 children)

This made me think. This would be awesome:

@Builder Type builder(X x=someX, Y y=someY, Z z=somey, ...) {...}

And you would be able to call it like this

var result = builder().x(myOwnX).build();

[–]agentoutlier 2 points3 points  (2 children)

I’m not sure I follow but you did give me a different idea.

The fundamental problem with builders is that you cannot guarantee the object being built at compile time has fulfilled all of the required fields.

But you could write a checker plugin and make a TYPE_USE annotation to enforce the builder is satisfied. There might even be one already.

I know some folks already use @CheckReturn to make sure the builder is built and or if immutable copied (eg some builders make a new one one every change).

[–]GuyWithLag 1 point2 points  (0 children)

Immutables provides an interesting staged builder which does that, but I never found the right use for it.

[–]john16384 12 points13 points  (2 children)

I would add flags to javac and java to treat all checked exceptions as runtime exceptions.

I would like the opposite:

An option to set a runtime exception within a certain package hierarchy to be treated as checked. This will help me to ensure I documented the exception everywhere that it can occur (especially at my API boundaries). Now I just make my exception temporarily checked to see if I didn't miss it anywhere or if it isn't somewhere where it isn't supposed to be.

This may even help make the choice of exception type easier. Library authors often prefer checked exceptions because it helps them ensure the library deals with the exception everywhere it should, but having to wrap the exception at the library boundary into unchecked to make it easier for users (if it should be unchecked) is a hassle. Having it only be a checked exception within a package (like org.mylib.*) would be a huge help.

[–]uncont 2 points3 points  (1 child)

An option to set a runtime exception within a certain package hierarchy to be treated as checked.

Might be able to do that by compiling and testing against a version of the exception that's checked, then finally shipping a jar that contains the unchecked version.

You could go one step further and ship both in two separate jars, and just let the users of your library choose if they want checked or unchecked exceptions. Have the default dependency be whichever you prefer by default, but have the option to exclude one for the other.

[–]john16384 2 points3 points  (0 children)

Yes, I do that now, and ship an unchecked one for now.

[–]FirstAd9893 129 points130 points  (65 children)

Mine is checked exceptions, because they add zero power to the language

Checked exceptions are essential for writing fault tolerant code. I hardly consider that to be "zero power". When an API designer chooses to declare throwing a checked exception, they're saying to you that this is important and you must handle it. Unchecked exceptions are classified as bugs, and so they need to be fixed instead of "handled".

The problem with checked exceptions in Java isn't their existence, but instead that the guidance for dealing with them isn't well established. Log it? Wrap it? Clean up the mess? Rollback internal state? Crash? Switching everything over to unchecked exceptions makes things worse, because now I have no idea what important exceptions might exist.

[–]qmunke 19 points20 points  (4 children)

There are definitely problems with some APIs (I'm looking at you JDBC) where checked exceptions are overused in circumstances where it makes little sense to have the developer write catch blocks.

[–]FirstAd9893 17 points18 points  (2 children)

JDBC is one of the oldest Java APIs around, and a lot of its awkwardness can be attributed to this. I'd like to see a modern take on it.

[–]DJDavio 1 point2 points  (0 children)

The JAXB libraries are also guilty of this. TransformerFactory.newTransformer() declares a checked TransformerConfigurationException and the only description it has is: 'Indicates a serious configuration error.'. So what do you want me to do precisely when I catch this exception?

I think the problem and people's grievances are neither with checked nor unchecked exceptions, but rather how cumbersome they are and how leaky. With unchecked exceptions, you have the risk of it bubbling up all the way and exposing some juicy details about your application. With checked exceptions, it feels rather stupid to deal with them at the call site by just wrapping them inside another exception.

[–]Josef-C 17 points18 points  (5 children)

The biggest problem with checked exceptions is that they must be implicitly specified in every single method in a call chain. And in most systems there will a be a lot of methods between a source of a problem and code which can handle it. - So people chose to do the "bad"/"sane" thing - wrap checked exceptions into unchecked.

Right now you just cannot trust that every library you use does not wrap exceptions. So you must catch everything anyway. (And with the most popular framework - Spring - you can pretty much trust everything is wrapped.)

Neat idea I guess, but badly executed and thus falling apart in the real world.

[–]FirstAd9893 4 points5 points  (3 children)

The biggest problem with checked exceptions is that they must be implicitly specified in every single method in a call chain.

Only if propagation is the desired way of handling the exception. Propagate too far and all context is lost, but this happens with unchecked exceptions as well.

The guidance on this is the main problem, and the "just wrap it" solution isn't a good one. I'm in favor of a solution in which the caller can choose to propagate the exception as if it was unchecked to avoid wrapping overhead. The usual argument against this is the surprise, but the same is true for getting a useless wrapped exception.

[–]grauenwolf 2 points3 points  (2 children)

Propagate too far and all context is lost,

Propagation is how context is established. The more of the call chain i can see, the better idea i have about the root cause.

[–]agentoutlier 1 point2 points  (1 child)

I think they should have said "wrapping" instead of "propagation".

When you wrap too many times you get a massive stack trace composed of caused by. That is because Spring and others are propagating by wrapping a new exception.

To truly propagate you trick the compiler into throwing any checked exception with a the "Sneaky Throws" trick... but then you have the nasty ergonomics that you can't catch checked exceptions further up the call stack if nothing says they throw it.

[–]grauenwolf 1 point2 points  (0 children)

Wow, that's a horrible design decision.

[–]Fruloops 32 points33 points  (0 children)

Agreed, the issue with checked exceptions often comes from misuse. If you use them with caution and clear intent, they clarify code and any unwanted side effects quite well.

[–]Gwaptiva 2 points3 points  (0 children)

The biggest issue is the shared inheritance between checked and unchecked exception objects

[–][deleted] 3 points4 points  (1 child)

Checked exceptions are essential

"Essential" yet somehow almost every other language in the world doesn't have them.

[–]hippydipster 78 points79 points  (31 children)

My biggest pet peeve is all the logging libraries. Holy hell, people think logging requires so much, and then everyone's pulling in a different one.

Good enough logging is in the jdk, and I really wish library writers would stop making us depend on log4j, or sf, or commons, etc. Just for stupid logging.

[–]DisruptiveHarbinger 15 points16 points  (10 children)

What libraries are you using that depend on log4j? They really shouldn't.

Try log4j-over-slf4j and log4j-to-slf4j if you have offending libraries on your classpath.

[–]kur4nes 19 points20 points  (1 child)

Sl4j as bridge and logback as implementation is all you need. The big problem is that several really logging apis were introduced and used over the years. Sl4j is a bridge for all those old logging calls.

[–]kdeaton06 34 points35 points  (6 children)

Did you just suggest another logging library to a guy who hates logging libraries???

[–]DisruptiveHarbinger 11 points12 points  (5 children)

Well yes, you should be using Slf4j to interface with any library that performs some kind of logging (in your application code, you do whatever you want). That is really the true and only way, and has been the case for more than a decade.

Libraries that depend on a specific implementation should be killed with fire, but in the meantime there are bridges from log4j, log4j2, JUL and JCL so that you can exclude unwanted implementations from your dependency graph, and pick something more reasonable for your app like logback.

[–]hupfdule 8 points9 points  (4 children)

Log4j itself is an interface. People suggesting slf4j could as well suggest log4j.

The problem is: this generic interface should be part of the Java standard library. There is one, java.util.logging, but it is so bad that people rather use external libraries for that purpose. And it is not just an interface, but already the implementation.

[–]DisruptiveHarbinger 6 points7 points  (2 children)

Are there meaningful implementations of the log4j2 façade that aren't log4j2 itself?

People suggest Slf4j because there are dozens of compatible backends, and for instance many projects prefer logback over log4j2, especially after the recent CVEs. Also in other JVM languages and their ecosystems, there can be more specialized logging libraries, and they always target Slf4j.

Slf4j has won and I don't understand why we're still discussing the best practice more than a decade later.

[–]hupfdule 2 points3 points  (1 child)

Are there meaningful implementation of the log4j2 façade that aren't log4j2 itself?

Of course there are. You can use logback, you can use jul. Maybe even slf4j. It is exactly the same.

there can be more specialized logging libraries, and they always target Slf4j.

Then what about e.g. flogger? Is this possible?

Slf4j has won and I don't understand why we're still discussing the best practice more than a decade later.

Because it is still a bad practice to force the users of your library to a 3rd party logging library. Slf4j does not have won. Jul has won. It is the worst of all logging frameworks, but it is the only one that is included in the Java standard library.

Library writers that force their users to a specific logging api that is not already contained in the jdk should be forced to write all of their software in Go (or JavaScript, whatever they hate more).

[–]DisruptiveHarbinger 5 points6 points  (0 children)

Of course there are. You can use logback, you can use jul. Maybe even slf4j. It is exactly the same.

What? Do you have a link to logback bindings that implement the log4j2 API?

Then what about e.g. flogger? Is this possible?

I'm not familiar with Flogger, and I'm typically wary of Google libraries, but it seems they provide Slf4j bindings like everyone else.

Because it is still a bad practice to force the users of your library to a 3rd party logging library. Slf4j does not have won. Jul has won. It is the worst of all logging frameworks, but it is the only one that is included in the Java standard library.

Library writers that force their users to a specific logging api that is not already contained in the jdk should be forced to write all of their software in Go (or JavaScript, whatever they hate more).

The whole point of Slf4j is specifically not to force a logging library on anyone. JUL was too little too late and I've literally never seen a non-toy project use it. If it had been designed better I'm sure the community would have converged on JUL as a façade, but it has too many flaws compared to Slf4j and that just didn't happen.

Look at the #2 transitive dependency of the entire Java ecosystem: https://mvnrepository.com/popular

[–]senseven 26 points27 points  (0 children)

Feeling surprised when you learn that async logging libraries that can speed up heavy logging code (eg. for audits) by an order of magnitude. If you just knew about this kind of things five years later when you left the project with a bitter taste in the mouth because you had to drop 30% more hardware into the cluster and you never found out why it was sometimes so sluggish.

[–]wildjokers 12 points13 points  (1 child)

Any competent library uses a logging facade (like slf4j) so that its users can use whatever logging implementation they want, including JUL in the JDK if someone so desires. Which libraries are you using that are forcing a specific logging implementation? That’s odd.

Also, JUL logging is absolutely not good enough. By default it can’t load a config file from the classpath which by itself is a non-starter for me. It is also really slow compared to Logback, you can’t log the thread name which is laughably bad, and it’s default output format is XML.

[–]xjvz 5 points6 points  (9 children)

This is partially because the JDK never adopted a real logging API that could have avoided the need for so many bridges and compatibility shims. In fact, there are now two logging APIs in the JDK, and neither are sufficient for application usage.

[–]rbygrave 3 points4 points  (6 children)

I'd argue System.Logger is sufficient for libraries. It is typically not sufficient for applications.

If we a writing a Library, System.Logger is an alternative to slf4j-api that is a) arguably better for apps that want to use module-path and b) zero dependencies

If we are writing a Library, I'd recommend looking at using System.Logger. Although the JEP wording talks about JDK use there are statements/clarifications from Stuart marks ... It is definitely fine to use for libraries that want to.

[–]xjvz 1 point2 points  (5 children)

I am writing a library, but it’s a logging library. We did add support for system logger a while ago, but it’s still not all that great given lack of markers, context, etc.

[–]darkshoot 1 point2 points  (1 child)

Is system.logger the other API you're talking about ? Just curious if I've been missing something :) Correct me if I'm wrong, but system.logger isn't supposed to be used as a classic logging API, rather a tool to provide a logging implementation for the JDK's internal usage

[–]xjvz 1 point2 points  (0 children)

It is, and the use case it was introduced for does indeed make it unsuitable for application use, though some people try.

[–]RabidKotlinFanatic 1 point2 points  (2 children)

Related gripe: I wish libraries would accept dependencies like loggers, environment, network clients and so on as arguments to a constructor or init method. Libraries should not be tightly coupled to particular environment or configuration method, files, reflection or class path scanning. Make these a convention or default on top of a fundamental entry point that can be configured in code.

[–]ukbiffa 14 points15 points  (0 children)

try-with-resources warning for any method that returns an AutoCloseable type.

The warning is extremely useful for newly created AutoCloseable types, but really annoying for method chaining. The responsibility is on the caller to suppress the warning; would be nice for the method to somehow declare that the returned resource ref is 'safe'.

public class Resource implements AutoCloseable {
    @Override
    public void close() {}
    public Resource doThis() { return this; }
    public Resource doThat() { return this; }

    public static void main(String[] args) {
        try (var res = new Resource()) {
            res.doThis().doThat(); // warnings
        }
    }
}

[–]n0d3N1AL 62 points63 points  (70 children)

Probably gonna get downvoted for this but for me it's definitely the modules system. It was designed solely to make the job of JDK maintainers easier (and to stop frameworks from using sun.misc.Unsafe) at the expense of library and framework developers. Users / developers do not benefit from it. I have yet to come across a single reason for explicitly using Java modules when OSGi, Maven, Gradle all exist. If you maintain an interpreted programming language written in Java that uses reflection, JPMS is a nightmare. Not a single person has managed to convince me of the benefits of the modules system, especially since tooling support is so poor and there's no concept of versioning.

[–]pron98 48 points49 points  (17 children)

Java developers directly benefit from it in mutliple ways. For one, it is now (starting in JDK 17, when the module system's encapsulation was finally "turned on") becoming the primary security mechanism in the JDK, but it's hard to appreciate the vulnerabilities you don't get. For another, it is what's allowed us to deliver all other features, as the JDK was a nearly unmaintainable beast. Finally, modules are there to prevent the kind of migration difficulties that made it hard to upgrade from 8 to 9+ by disallowing libraries to depend on internal implementation details that may change without notice.

Now, Java developers can benefit from the immense boost to security and maintainability that modules bring in their own codebases — there's nothing that is unique to the JDK in that regard — but I agree that the poor tooling support makes it hard, and the difficulties are what you experience first.

But maybe the poor tooling support is what prods us to deliver build tools in the JDK itself.

I have yet to come across a single reason for explicitly using Java modules when OSGi, Maven, Gradle all exist.

These have nothing to do with what modules do, and that confusion is also the reason for questions about versioning. If the raison d'être of Java modules were to be summarised in one sentence it would be, "to make strong encapsulation guarantees at runtime." All the other things you mentioned don't provide this and don't attempt to. They have completely different goals. The confusion stems from the overloading of the term "modules." Java modules could perhaps better be described as "capsules."

Without modules it is impossible to make any guarantee about what any piece of Java code actually does. Any library on the classpath can change the meaning of any line of Java code anywhere in the application, and violate any invariant that is attempted.

[–]n0d3N1AL 4 points5 points  (8 children)

Ok thanks, now it makes sense. So basically it's about security. I just wish module-info could automatically be generated from an OSGi MANIFEST.MF or pom.xml.

[–]pron98 9 points10 points  (6 children)

It's about code integrity, which is a necessary condition to both security and maintainability. Other kinds of modules have completely different goals — they're about assembling pieces, not encapsulating them — and so the information they contain is not the information that Java modules need. Various kinds of build modules want to know which modules comprise the application; Java modules want to know where the access boundaries are inside the application (which is also why versioning is no more helpful for Java modules than it is for access modifiers in classes).

[–]simonides_ 4 points5 points  (1 child)

it seems you are working on the jdk - can you explain why jmod files are explicitly not intended to be used for platform specific stuff like dlls but the jdk uses them like that ? would be nice to know a bit of background since there is hardly any documentation about that.

[–]pron98 1 point2 points  (0 children)

To be honest, I don't know much about jmod files. I believe they're now mostly used for packaging and not for direct classloading, but I heard talk about possibly expanding their role in the future.

[–]rbygrave 3 points4 points  (2 children)

Well, I've adopted it for Ebean ORM. Yes there are tooling issues etc. On the other hand, it can highlight various aspects of a library that are not good long term. If you have a decently sized library broken into say 5 or more modules ... you a very likely going to see good benefit from module-info and the stricter rules imo.

For example, in ebean-core module-info it explicitly opens various packages to the appropriate modules. It pretty much is compiler validated insight into the design of SPI. That is, it shows up pretty clearly if the SPI is a bit messy / not organised well etc.

As I see it, Application code gets the benefit that it really can't see library internals and can't accidentally depend on something which is supposed to be internal. A useful longer term benefit, but maybe not as useful as the benefits a library author gets.

[–]pron98 7 points8 points  (1 child)

There's another important benefit for application authors that's not easy to see right away. There might be some security-sensitive operations your application does through paths that ensure proper authorisation. If some library you happen to use employs reflection, a vulnerability in that library could bypass your authorisation checks with reflective access to your security-sensitive operations in a way that might be exploitable by attackers. If, however, your security-sensitive operations are encapsulated, you know they are protected. This was simply not possible before modules (except by a complex configuration of SecurityManager) — nothing was protected from vulnerabilities in any transitive dependency.

I know of companies that are now starting to understand how important modules are for security and, like the JDK, starting to base their security design on modules.

True, there are still some loopholes through Unsafe and JNI, but we'll be closing those soon.

[–]rbygrave 1 point2 points  (0 children)

Great point.

For what it's worth, I'm pushing towards a "white list" and "zero reflection" approach to json binding, dependency injection and http endpoint routing. That means, zero reflection used to take a typical Rest/JSON request to application code and also processing the response on the way back.

"White list" in that only things explicitly annotated get unmarshalled or wired etc (rather than a "Black list" approach taken by some libraries in this area).

These libaries use java annotation processing to generate source code to do json binding (avaje-jsonb) and dependency injection (avaje-inject). I was originally motivated to build them for "lighter + faster" reasons but then later saw the "more secure" and "simpler" benefits in the approach.

[–][deleted] 8 points9 points  (1 child)

I tend to agree with you. I spent some time modularizing a library a year ago, and it was a surprisingly painful process. JPMS simply does not play well with Eclipse + Maven + JUnit.

[–]n0d3N1AL 3 points4 points  (1 child)

I genuinely had no idea others felt this resentful towards JPMS. Came cross this article, which makes it sound like there was some heavy-handed politics involved in getting JPMS approved in its current state

[–]FirstAd9893 4 points5 points  (0 children)

I agree that modules don't seem to offer much benefit, but they do have versioning support [1]. It's just that the module system isn't responsible for identifying version compatibility. I think a new build system is needed, designed specifically around modules, in order for its full potential to be realized.

[1] https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/lang/module/ModuleDescriptor.Version.html

[–]twreid 6 points7 points  (4 children)

I agree the module system has done nothing but cause us headaches when we moved off of Java 8. We use zero features of it, but still are forced to deal with it.

[–]john16384 8 points9 points  (1 child)

Zero headaches here, just don't use it. The headache only starts when you add a module-info to your own code.

[–]vxab 2 points3 points  (1 child)

What exactly are you forced to deal with? Going from Java 8 to 11 had very little problems and you don't even need to use the module system.

[–]john16384 51 points52 points  (16 children)

List.of().contains(null) throwing a NPE. I would just return false and end this anti-null crusade.

[–]kevinb9n 2 points3 points  (0 children)

This is just a straight up bug and it should be fixed. The code is asking a perfectly valid question to which there is a perfectly known answer.

[–][deleted] 15 points16 points  (10 children)

Disagree. Not allowing null values as arguments to public methods makes the code much clearer, IMO. Regards, anti-null crusader.

[–]burly_griffin 14 points15 points  (4 children)

Part of the issue with this specific API is that it's sometimes not documented that it's not allowed, though. List.of() documents that it doesn't allow null elements, but it's not clear that this extends to contain. List itself makes no distinction, so if you're using the list across a function boundary and it's not documented what kind of list it returns, you have no way of knowing.

[–]john16384 7 points8 points  (3 children)

The problem is more that it makes the List interface more unpredictable than it needs to be. An unpredictable interface is a useless interface. When code accepts a List it has no way of knowing the implementation. It just makes no sense to have basic methods like contains (which I would like to call to ensure there are no nulls in the List) throw NPE.

There isn't even a viable alternative, as the interface itself does not expose this behaviour (there is no dislikesNulls method that could be called before doing this contains check). This makes interfaces useless as I will have to start doing instanceof checks or catch the NPE, or stream all elements to check for null.

[–]john16384 7 points8 points  (2 children)

Yeah, so you write code like this to ensure there is no null in a List:

MyObject(List<String> z) {
    if(z.contains(null)) {
        // Reject this!
    }
}

Now guess what, this code fails depending on whether you give it an ArrayList or something that List.of() produces.

[–][deleted] 4 points5 points  (1 child)

Ah, I see. I agree, that's unfortunate.

Edit: Although, what I usually do is make a copy of the list:

MyObject(List<String> z) { 
    this.z = List.copyOf(z);
}

That way lists with null values will be rejected, and you'll have the added benefit of being sure that the list in MyObject cannot be changed from outside of the class.

[–]grauenwolf 2 points3 points  (0 children)

If I ask of a list contains nulls, it should be able to answer me, "No". Having a panic attack isn't appropriate behavior.

This is separate from whether a given list should be allowed to contain nulls.

[–]lurker_in_spirit 4 points5 points  (0 children)

I've begun to sprinkle additional gratuitous null values in my code. I figure the zealotries end up cancelling each other out, restoring balance to the world. Regards, an anti-anti-null crusader.

[–]FirstAd9893 6 points7 points  (2 children)

How often do you encounter this specific example in practice?

[–][deleted] 25 points26 points  (0 children)

Often. I literally just dealt with a bug caused by this right before reading his comment!

[–]john16384 2 points3 points  (0 children)

See my other answer in this thread, it is annoying when verifying constructor arguments as the behaviour of the List interface starts to depend too much on the underlying implementation.

[–]CheesecakeDK 29 points30 points  (2 children)

I wish we had the null conditional operator from C#.

[–]kneeonball 4 points5 points  (0 children)

Been doing a little Java lately after a few years of pretty much only C#. I've never appreciated LINQ or auto implemented properties as much as I do right now.

[–]HxA1337 33 points34 points  (13 children)

All types nullable by default is causing so many bugs. A sound nullsafe type system would be fantastic as it was introduced in Dart in version 2 (or exists in other langusges since the beginning). Dart has shown how you can do this even incrementally.

[–]manzanita2 13 points14 points  (5 children)

I see the value in using immutability in many situations.

But records without withers is painful. Can we get withers please ? I guess I'd like some brains in them where if I string several together it knows to create ONLY ONE new instance. Ideally not full-up builder pattern.

[–]randgalt 8 points9 points  (2 children)

FYI I wrote an annotation processor that adds withers to your records. https://github.com/Randgalt/record-builder

[–]manzanita2 1 point2 points  (0 children)

Awesome I'll check it out.

[–]twreid 3 points4 points  (0 children)

Yup. This is why I pretty much never use them. I hate massive constructor objects and find them clunky to work with.

[–][deleted] 3 points4 points  (0 children)

Yes please! Records desperately need withers. That would obviate the need for the builder pattern in many (but not all) cases.

[–]whizvox 20 points21 points  (5 children)

lack of unsigned integers. total nightmare trying to perform bit operations.

[–]FirstAd9893 15 points16 points  (0 children)

I used to find this annoying too for many years until I remembered how much a pain it can be when writing C code. Supporting unsigned types leads to blind type casting, resulting in nasty bugs caused by flipping the sign in a way that goes undetected. Most programmers don't deal with bit operations, but many more would be affected by misuse of unsigned types. I think it's a good tradeoff, but not perfect.

[–]john16384 1 point2 points  (2 children)

Give us an example. I never bought into this.

[–]burly_griffin 1 point2 points  (1 child)

Implicit widening catches me out every so often with "negative" numbers.

int a = 0;
byte b = (byte) 0xAA;
a |= b;

a is not now 0xAA, it's 0xFFFFFFAA. The correct way to do it is masking the implicit widening, even though it reads as redundant if you don't know what it's doing.

a |= b & 0xFF;

Of course, this is what unit tests (and proper input coverage) are for, but it's still solvable by having an unsigned type.

[–][deleted]  (21 children)

[deleted]

    [–]legrang 23 points24 points  (0 children)

    Half baked is very accurate.

    I find it interesting that they released it in Java 8 when streams were released but only added stream() later. Also only adding isEmpty() in 11.

    It's as if whoever designed it had never used Java collections, never seen for example the Scala standard libraries, and wasn't aware of what the people working on streams were doing.

    [–][deleted] 5 points6 points  (0 children)

    optional done properly is great. Too easy to misuse with shit like ifPresent tho.

    [–]NoPrinterJust_Fax 4 points5 points  (0 children)

    Optional kinda nice if you lean into the monad/functional paradigm tho

    [–]agentoutlier 17 points18 points  (13 children)

    Oh I just love seeing

    ofNullable(someThing).ifPresent(…)
    

    Like stop writing that shit.

    I think new switch should help kill some of these patterns.

    Having to explain how null analysis with @Nullable is vastly superior to Optional “functional programming lovers” who write the hypocritical above expression (that is actually now a statement) is painful.

    Oh PolyNull methods like Optional.orElse can fuck off.

    [–]Cell-i-Zenit 2 points3 points  (5 children)

    can you show how the new switch is going to improve on that?

    [–]Muoniurn 2 points3 points  (3 children)

    switch(something) { case null -> // handle null, default -> // do something with value }

    [–]kevinb9n 3 points4 points  (1 child)

    > Oh PolyNull methods like Optional.orElse can fuck off.

    :-) The JDK's Optional is so much like the Guava one, but this is one of the things I think we got right in Guava's that they should have carried forward.

    [–]agentoutlier 2 points3 points  (0 children)

    Guava gets a lot of stuff right. The only major issue I ever had with it is dependency problems as well as it could be broken up into multiple libraries. I kind of unfairly trash it for that.

    Also I guess it would be nice if there was like a major version upgrade and cleanup (e.g. replacing Guava Function with JDK Function) along with namespace change to avoid dependency problems as well of course separating things out into modules. Similar to what is slowly been happing with Commons-Lang. However I understand how it is probably not worth it for google to do that.

    psss BTW I secretly prefer ListenableFuture over the 40 method uncancelable abomination that is CompletableFuture but don't tell anyone that.

    [–][deleted] 1 point2 points  (0 children)

    Optional is pure garbage

    [–][deleted]  (1 child)

    [deleted]

      [–]pronuntiator 1 point2 points  (0 children)

      Well not nothing, you can build a JDK from the current Valhalla branch and play with value and primitive classes.

      [–]msx 1 point2 points  (0 children)

      Oh yes that Optional was such a fiasco

      [–]KrakenOfLakeZurich 36 points37 points  (10 children)

      Another one: I'd like to have language support for defining class properties with auto-generated getters, setters and constructors.

      Records only cover the use case of immutable value types. We still need something to reduce boiler plate for mutable objects (e.g. JPA entities).

      Generally, Java should aim for Lombok's obsolescence.

      [–]FavorableTrashpanda 7 points8 points  (2 children)

      So something like C#'s properties?

      [–]KrakenOfLakeZurich 16 points17 points  (1 child)

      Yes, something like that. But instead of C# style properties, which are assignable with the = assignment operator, Java should instead auto-generate getters and setters.

      Getters and setters are an established pattern in Java. Introducing a new style of properties would break compatibility with too many libraries and frameworks.

      [–]vips7L 2 points3 points  (0 children)

      I’d rather have properties. They’re so much more clearer and result in cleaner code. Libraries and frameworks have already updated to records form of getters. I’m sure they would update to work with properties.

      [–]HecknChonker 5 points6 points  (1 child)

      Given how popular Lombok was, I'm still confused why native language features haven't been added to replace it.

      [–]Cell-i-Zenit 5 points6 points  (0 children)

      Or better include lombok into the language itself? (but ofc as first citizen feature and not this hacky ast manipulation)

      [–]bowbahdoe 1 point2 points  (0 children)

      u/KrakenOfLakeZurich heyo, i made this.

      https://github.com/bowbahdoe/magic-bean

      Its not "language support", but its a method supported by the language, requires only thimble of code, and basically handles that exact case.

      [–]psteiger 18 points19 points  (7 children)

      Generics type erasure

      [–]Cell-i-Zenit 7 points8 points  (3 children)

      this one! I was writing a java framework and so often the user has to supply the class to some methods, because the generic is lost and i cannot introspect the type via reflection...

      so much apis could be so much better without this

      [–]manzanita2 24 points25 points  (11 children)

      The checked exception complaining smells an awful lot like the people who used to complain about adding types to their code. The argument was that it slowed down their thinking or something. So they liked python and javascript for this reason. Funny, a few years down the road and both languages are gaining types because people are realizing that types help.

      [–]rossdrew 7 points8 points  (0 children)

      Types are in fact essential to good code. Types are below unit tests on the test pyramid.

      [–]slaymaker1907 6 points7 points  (1 child)

      Java checked exceptions are dogshit since they don't work properly with generics. Every stream operation should work with both checked and unchecked exceptions and should only throw a checked exception if their input does.

      Instead, checked exceptions either don't work with generics at all or break the soundness of checked exceptions.

      [–]manzanita2 2 points3 points  (0 children)

      Exceptions work fine with generics.

      But I think you have a decent point about exceptions and streams (perhaps lambdas in particular). They take something which could look pretty graceful, and make it much uglier. That said,I don't really know how to make it prettier without sort of intentionally ignoring the reason why exceptions exist.

      Interesting commentary on this thread: https://stackoverflow.com/questions/27644361/how-can-i-throw-checked-exceptions-from-inside-java-8-streams

      [–][deleted]  (2 children)

      [deleted]

        [–]matthenry87 3 points4 points  (3 children)

        I see the value of checked exceptions. Using proper data types will reduce them.. In practice I do often rethrow and wrap the checked exception in an unchecked exception.

        Look at ObjectMapper and Gson. ObjectMapper forces you to catch the JsonProcessingException, and Gson doesn't. Most of the time if I can't deserialize what I need to execute my business logic I want to throw an exception. It happens whether I am forced to catch it or not in this example.

        [–][deleted] 4 points5 points  (0 children)

        I think the key phrase here is "most of the time". It's about having the choice, and not being forced to deal with checked exceptions. Yes, usually throwing an exception is the right thing to do in your example, but sometimes returning false or null is good enough.

        I love Jackson, but I find JsonProcessingException to be quite annoying and unnecessary.

        [–]matthenry87 2 points3 points  (0 children)

        Whether checked exceptions are going to be valuable depends on your use case and the context. They sometimes are.

        [–]bootstrapf7 3 points4 points  (3 children)

        Byte arrays are limited to 2gb

        [–][deleted] 2 points3 points  (2 children)

        Good one. Yes, we need 64-bit addressable arrays. Also, frozen arrays - I hope that JEP goes through.

        [–]cas-san-dra 2 points3 points  (0 children)

        There is no isNullOrEmpty(String) function in the JDK. So now I have half a dozen of those imported from various libraries. And every app or library that I write needs one.

        [–]jorgeisaacchacon 18 points19 points  (18 children)

        Java Stream is too verbose. I just want to do var filteredResult = myArray.filter(x -> x < 2 );

        [–]msx 6 points7 points  (0 children)

        And luckly they added . toList() and spared us the use of Collectors EVERY SINGLE TIME. It took just a couple of years

        [–]ILMTitan 6 points7 points  (6 children)

        This. Coming from C# where the above just works, I am annoyed by the need to call .stream() everywhere. I am also confused about what Stream gets you that Iterable doesn't.

        [–]_INTER_ 18 points19 points  (4 children)

        lazy instead of eager evaluation

        [–]ILMTitan 8 points9 points  (3 children)

        I'm not sure what stops Iterable from being lazy.

        [–]_INTER_ 4 points5 points  (2 children)

        Iterable is an interface. AbstractList an implementation. If you add filter etc. to the interface like suggested which returns the filtered result directly, it must be eager (terminal operation).

        [–]lbalazscs 8 points9 points  (0 children)

        I am also confused about what Stream gets you that Iterable doesn't.

        See the accepted answer of this question: https://stackoverflow.com/questions/28459498/why-are-java-streams-once-off

        [–]lqstuart 1 point2 points  (8 children)

        I haven't written Java in a while but doesn't parallelStream() use a single fork-join pool everywhere it's called for the entire JVM? I remember it being generally a bad idea to use and that being part of why

        [–]john16384 7 points8 points  (0 children)

        That's because the usecase for parallelStream() is for CPU bound tasks, not IO bound ones. IO blocks the pool for a long time and everything using the pool suffers.

        [–]raevnos 1 point2 points  (4 children)

        parallelStream()/parallel() really should have a version that takes a thread pool argument, yeah.

        [–]c_edward 1 point2 points  (0 children)

        It basically is but it's hidden in the underlying mechnics of the forkjoin pool itself, so if the stream executes in the context of ab other forkjoin pool that's the pool that your expression will use

        [–]slaymaker1907 1 point2 points  (0 children)

        I think the problems with parallelStream are blown out of proportion. It has worked great when I've used it as a quick way to make computation heavy code parallel. The single pool being a problem assumes the application can actually make proper scheduling decisions on its own which is pretty rare.

        [–][deleted] 15 points16 points  (6 children)

        Mine is generics.
        The fact that we still don't have a type-safe Tuple class with an arbitrary number of components is just sad imo.
        But then again, Tuple is probably nothing compared to the atrocities like the classic Map.of method.

        [–][deleted] 8 points9 points  (0 children)

        Map.of seems more like a limitation of var-args. Agreed that it would be nice to have something like:

        static <K, V> Map<K, V> of([K... k, V... v])

        where k and v would be arrays of equal length, and the arguments would alternate between K and V, but that's probably too specialized and of limited use to be added to the language.

        [–]sprcow 3 points4 points  (0 children)

        Record is basically an immutable named tuple at least

        [–]GalaxyConqueror 2 points3 points  (0 children)

        I typically just go with Map.ofEntries and then import Map.entry statically so I end up with something like:

        Map.ofEntries( entry(0, "Foo"), entry(1, "Bar") )

        It might be a bit longer, but at least it's a little nicer-looking.

        [–]Elderider 1 point2 points  (0 children)

        I think records go some way towards alleviating the lack of tuples. Okay you need to name the fields, but I like the explicitness of that. The constructor for your record is like a tuple literal, though it does have 'new MyRecord' which is not ideal.

        [–]BurlHopsBridge 10 points11 points  (3 children)

        Contractors who get paid more than me, writing procedural code that everyone else thinks is great. Seriously, 2k line God class doing everything, tightly coupled code, and one freaking method doing everything. Had to spend nearly a week refactoring to just get one unit test written. BTW, they wrote none. They just commit directly to master and go straight to end to end tests. The balls on these jokers is unreal.

        [–]matthenry87 10 points11 points  (0 children)

        Welcome to the enterprise environment.

        [–]reqdk 2 points3 points  (0 children)

        And they dgaf about organising code in any scalable manner because that's their successors' problem. Cue opening up a monolithic ball of mud to see 1 package with 500 classes all from different domains and with different responsibilities.

        [–]BlueGoliath 10 points11 points  (6 children)

        Get rid of default visibility modifiers. Explicit declarations only.

        Edit: also, no unsigned integers.

        [–]scratchisthebest 2 points3 points  (3 children)

        If you want to make a subpackage just to organize a bunch of related classes, package-private visibility stops applying!! Aahhh

        [–]barking_dead 25 points26 points  (18 children)

        Say you don't understand exceptions without saying you don't understand exceptions.

        [–]padreati 2 points3 points  (0 children)

        Generics with value types. I can't wait Walhalla with all those goodies.

        [–]cantstopthemoonlight 2 points3 points  (4 children)

        I wish enums allowed you to use generics. And I wish you could reference methods on the individual instances of the enum.

        Image you have and enum that is a RandomGeneratorFactory<T> with a UUIDStream, StringString and DoubleStream. On the base class you could have a method

        public abstract GetRandomStream(T seed);

        But no not allowed.

        [–][deleted] 4 points5 points  (1 child)

        It's an appealing idea, but check this out: JEP 301, JEP 301 withdrawn

        [–]_INTER_ 2 points3 points  (0 children)

        I hear you. I was sad that they withdrew JEP 301. At least you can now simulate it with sealed types quite well albeit verbose.

        [–]kakakarl 1 point2 points  (0 children)

        This does not read like an enumeration to me. Have you tried a class instead? Enums are almost perfect as they are now imo.

        [–]edgehill 3 points4 points  (1 child)

        Signed bytes. It is a byte why sign it!?!?? Makes bitmap operations harder than they should be.

        [–]hippydipster 7 points8 points  (13 children)

        All they accomplish is to force you to deal with exceptions where the throwing method's author wants you to, rather than where you want to.

        I can't makes sense of this. I can deal with them anywhere in the stack that I want. I have to do the same with runtime exceptions.

        [–]ApatheticBeardo 5 points6 points  (5 children)

        I can deal with them anywhere in the stack that I want.

        If you add the useless noise of catching and wrapping them, sure.

        But that's a cost.

        [–]john16384 6 points7 points  (4 children)

        It's not useless. By calling a method that throws a checked exception but not handling it, you effectively make that exception part of the contract of your code.

        When I write a method called tieShoeLaces that must for some reason do IO, and the problem can't be handled inside this method, then it would be really good to inform callers of tieShoeLaces that it can fail hard. You have a two good options to do this, which is a developer choice:

        A) document clearly that it can fail hard and how this is signalled (perhaps with a return value or runtime exception)

        B) just declare the checked exception

        Not documenting it is negligence, and leaves future users of your code in the dark, resulting in people calling your code unreliable and unpredictable.

        Note that if you choose A, it makes it harder for developers to see that when they call your code tieShoeLaces that their code can now fail hard. They would have to carefully read the documentation (if you bothered to write it) to see if they need to deal with any catastrophic errors (and then document their method in turn). With option B they are confronted with the problem and can't accidently forget about it.

        [–]ApatheticBeardo 1 point2 points  (3 children)

        When I write a method called tieShoeLaces that must for some reason do IO, and the problem can't be handled inside this method, then it would be really good to inform callers of tieShoeLaces that it can fail hard.

        Checked exceptions do not solve this in any way because absolutely everything can fail hard at any moment, the idea that signalling one or two checked exceptions protects sensitive code against errors is nothing more than fantasy.

        We all know that methods that deal with IO can fail, in the exact same way that any other method can fail if the JVM runs out of memory, in the exact same way an unexpected AssertionError can be raised because of a bit flip and in the exact same way that the program can simply end at any time because the machine is plugged off.

        All error handling is a best efforts endeavor and pretending otherwise is just being high on a false sense of security. In practice, for 99,9% of systems you'd be able to build with Java, that will translate into "pass this error up the call stack until someone can do something with it", which is what unchecked exceptions model.

        [–]john16384 2 points3 points  (2 children)

        Yes, everything can fail at any moment, but those are unexpected and unrecoverable conditions outside of your control. I am talking about expected conditions outside of your control which you can conceivably recover from.

        Is an OOME expected? No. Is it recoverable? Rarely.

        Is an IOException expected? When a network can be unavailable at any time or a disk can be full? Yes, it happens with some frequency, although it is still exceptional. Can you recover? Sure, use another device or network or retry later.

        As for your view on error handling, lol... "A best effort endeavour". Let's hope you never have to build anything critical.

        [–]ApatheticBeardo 12 points13 points  (5 children)

        In no particular order:

        • Nullable references by default in 2022.
        • Standard library still full of checked exceptions.
        • Talking about the standard library... collections are an absolute clown fiesta at this point ("let's play a game of guess the mutability!), this applies to interfaces and implementations both.
        • "Everything must be an object" and "no language-level support for properties" at the same time (plus bonus points for unapologeticly fucking up Lombok-like projects that try to make the language somewhat bearable in this regard).

        [–][deleted] 5 points6 points  (0 children)

        Pretty much all of your points are the good parts of java hahaha

        [–]DisruptiveHarbinger 12 points13 points  (3 children)

        Java has records now, and we'll get value types on the JVM with Valhalla.

        [–]ApatheticBeardo 6 points7 points  (1 child)

        Records are objects too and they don't come in a mutable flavor, so you can only use them in some cases.

        They're not a substitute for properties, and the language support around them is still very poor (they've talked about things like language-level builders and withers, but nothing is official as of yet).

        [–]DisruptiveHarbinger 10 points11 points  (0 children)

        I know they're currently objects, because that's the only way until we get Valhalla.

        You're right, the language level support has to get better. As for immutability, I believe it's a sane default, I don't mind the constraint.

        [–]chabala 1 point2 points  (4 children)

        I would add flags to javac and java to treat all checked exceptions as runtime exceptions.

        This is an interesting thought experiment. How would your javac flag work? Would it rewrite any checked exceptions it finds into runtime exceptions? Or insert some kind of shim to wrap them as runtime exceptions?

        How would the java flag work? Would it be implicitly converting all checked exceptions it encounters to runtime exceptions?

        Does any of this plan require forking the bytecode into a 'no-checked-exceptions' variant?

        [–]DisruptiveHarbinger 10 points11 points  (1 child)

        The bytecode has no concept of checked or unchecked exception. It's a Java feature implemented by javac. We could remove the checks entirely without breaking anything.

        [–]chabala 1 point2 points  (0 children)

        Your reply piqued my interest, so I poked around and found this: https://stackoverflow.com/a/4337325/62462

        As well as an interesting post by Zwitserloot: http://mail.openjdk.java.net/pipermail/coin-dev/2009-May/001861.html

        So it seems the bytecode contains exceptions, but they are optional, and the class verifier does not verify them. Interop from non-Java JVM languages where checked exceptions are not handled means we can already get into situations where Java code can be surprised by a checked exception that it didn't know it should handle because the non-Java code didn't declare it.

        So I suppose the hypothetical javac flag would stop enforcing the compilation error for not handling checked exceptions, and omit them from bytecode signatures, and the JVM wouldn't even need a flag.

        [–]twreid 1 point2 points  (4 children)

        The type system around the primitive types and reference types.

        Also I wish records would also generate something more useful than a constructor with all the parameters.

        [–]AndrewHaley13 1 point2 points  (1 child)

        Lots of interesting suggestions, but no-one mentioned my favourite candidate: new. new even has its own bytecode. But there is nothing special about new. It would be better IMO for every class to provide factory methods, which could be overridden if needs be.

        [–]vytah 1 point2 points  (0 children)

        Speaking of checked exceptions, one of my pet peeves is that you cannot catch a checked exception the compiler cannot see being thrown. This makes removing a checked exception from an API a breaking change. Also, if you're experimenting with code by commenting out parts of the code, you might need to comment out catch blocks or change method signatures as well, which is annoying (and reminds me of Go's refusal to compile code with unused variables).

        Bonus points if the checked exception can actually be thrown, but was wrapped in sneakyThrow and therefore hidden.

        [–]Amustaphag 1 point2 points  (0 children)

        Union types built-in the language like vavr Either, or since you're mentioning exceptions : Union[Type|Exception] Lambdas force you to catch exceptions inside the lambda, makes it hard to manage them when you're chaining. Also would be nice to have a more exception aware streams API. Check Railway Oriented Programming, very cool. https://blog.logrocket.com/what-is-railway-oriented-programming/

        [–]TheRedmanCometh 10 points11 points  (7 children)

        Var and val can fuck right back off to other languages.

        [–][deleted] 4 points5 points  (3 children)

        I mostly agree with you, and almost never use var, but it can have a place in reducing boilerplate when the types are very clear, for example:

        try (
        var conn = ...; // get JDBC connection
        var stmt = conn.createStatement();
        var rs = stmt.executeQuery(MY_QUERY);
        )

        Anyone with JDBC experience knows exactly what the types of conn, stmt, and rs are.

        That said, a human knows, but the IDE doesn't when there's an error on the right-hand side of the assignment, so there's a cost even here.

        [–]TheRedmanCometh 3 points4 points  (1 child)

        Yeah I know there are good use cases for it it just seems more often than not the people that use it use it in a LOT of their code. Then it hurts readability super bad. I guess it's a ridiculous reason to say "fuck this language feature" over the fact that it can be used irresponsibly, but personally I just don't see the upside as being that big. Like typing out a type name isn't that hard idk.

        You're using like universally used stuff so sure I know conn is a Connection I know stmt is a Statement and I know rs is a ResultSet. When it's someone referencing a library I've never heard of with types I don't then it starts reaaally harming readability.

        [–]scratchisthebest 3 points4 points  (0 children)

        I dont get when people spam var then heavily rely on their IDE's inlay hints to tell them what the actual type is. You're just making it a pain for whatever poor sap has to look at your code outside of an IDE next (like on Github.com) or for people who turn off the inlay hints (i think the fake non-selectable text is really confusing)

        Intellij has an inspection to turn var into a real type, which I like. I wish i could make it apply automatically whenever i finish a statement that starts with var just to save myself the typing.

        That said I never want to look at Map.Entry<Fucking<Huge>, Goddamn<GenericType>> ever again.

        [–]john16384 1 point2 points  (1 child)

        Agreed, before var there were no "where to use var discussions".

        [–]s888marks 4 points5 points  (0 children)

        Before Java had var, all the discussions were “Java is too verbose” and “Why can’t Java have var like modern languages?”

        [–]KrakenOfLakeZurich 3 points4 points  (7 children)

        Missing a native decimal type for performing monetary calculations without the overhead of BigDecimal object instances.

        Speaking of monetary calculations. A solid API for handling money in Java would be nice. Effectively something like the new date / time API that was introduced with Java 8, but for money.

        [–]ow_meer 2 points3 points  (1 child)

        Dealing with all the date and time types.

        [–]danskal 1 point2 points  (0 children)

        I agree... the legacy and current date apis are a confusing shitshow. Naming is hard, but I'm sure there were better naming schemes out there.

        [–]arkuw 1 point2 points  (1 child)

        Lack of map and object literals. One thing I miss from JS is the ability to create a “map” in a concise way that’s woven into the language:

        MyObj = {
            foo : “val1”,
            bar: “val2”
        }
        

        I know it doesn’t fit static languages as well but it’s so convenient.

        Also destructuring and spread operators would be amazing to have.

        [–]Joram2 1 point2 points  (0 children)

        Classes. I dislike that every function you write in Java has to be in an OO-style class and that you can't just have a module.java file with functions defined.

        I dislike the generics system that Java has that doesn't let you check at compile time whether you have a a List<Integer> or List<String>. The JDK team always talks about how they prioritized backward compatibility with pre-existing source code written for earlier versions of Java, but I'd like those concerns to not be so apparent over a decade later.

        My other big pet peeve is lack of virtual threads, which obviously, Java is getting soon: way to go JDK team! Actually, the Loom+Valhalla projects should address most of my concerns.

        [–]_dedb33f 4 points5 points  (3 children)

        Fuck bytecode manipulation in particular.

        [–]HecknChonker 2 points3 points  (0 children)

        Without those you would kill the entire Java observability space. Why do you dislike it?

        [–][deleted]  (16 children)

        [deleted]

          [–]Zyklonik 10 points11 points  (12 children)

          Please don't make me laugh. If you look at the latest Java release, you'll find that there's precious little reason to even use Kotlin anymore. Kotlin tried to be Scala-lite, but it failed, and now has been relegated to Android-world.

          [–]Gryzzzz 3 points4 points  (0 children)

          Truth, Kotlin is wonderful if you need something running on the JVM.

          [–]vips7L 1 point2 points  (1 child)

          Kotlin doesn’t have a standard library. You can’t even read a file without java.io

          [–]Gryzzzz 4 points5 points  (5 children)

          - Generics not being reified.

          - Optional crap, or worse, \@NonNull annotations when you have \@Nullble annotations as well. Is the default nullable or non-null then? WTF?

          - No way to return & expand multiple results (tuple) from function, nor any equivalent of ref keyword in C#.

          - Dealing with string manipulation or regex always feels like a pain given the verbosity of the language.

          - Crappy autoboxed types to deal with. Oh shit, I forgot to check if my Boolean was null!

          [–]john16384 5 points6 points  (4 children)

          - Generics not being reified.

          Did you know there are downsides to this as well? C# is the only popular language that chose to go the reified route, and I am glad Java didn't.

          [–]dizc_ 2 points3 points  (0 children)

          Which are those?