Collection Factories, Flow API, Migration to the Module System

CodeFX Weekly #27–21st of July 2017

Nicolai Parlog
Jul 25, 2017 · 7 min read

Hi everyone,

there were some good Java 9 questions on StackOverflow this week: Why do the collection factory methods not accept null? Why do the returned collections randomize iteration order? Who keeps track of requested items in the new Flow API? How do you access resources in Java 9? Why does the unnamed module exist? I took some time replying to a few of them and will share the answers here. On top of that comes a primer for depending on unmodularized projects.

Originally, I wanted to write about internal APIs as well and how concerns for backwards compatibility in that area considerably increased the module system’s complexity but that turned out to be longer than expected, so I pushed it to next week.

I send this newsletter out every Friday. Yes, as an actual email. Subscribe!


Nulls in collection factory methods

On Stackoverflow someone asked why the collection factory methods do not accept null keys, values, or entries. Particularly highlighted was Map::of because, after all, HashMap does allow null keys and values.

Is that even allowed?

As other comments and answers point out, the Map contract of ourse allows nulls:

[S]ome implementations prohibit null keys and values [...]. Attempting to insert an ineligible key or value throws an unchecked exception, typically NullPointerException or ClassCastException.

And the collection factories (not just on maps) make use of that:

They disallow null keys and values. Attempts to create them with null keys or values result in NullPointerException.

But why?

Allowing null in collections is by now seen as a design error. This has a variety of reasons. A good one is usability, where the most prominent trouble maker is Map::get. If it returns null, it is unclear whether the key is missing or the value was null. Generally speaking, collections that are guaranteed null free are easier to use. Implementation wise, they also require less special casing, making the code easier to maintain and more performant.

You can listen to Stuart Marks explain it in this talk but JEP 269 (the one that introduced the factory methods) summarizes it as well:

Null elements, keys, and values will be disallowed. (No recently introduced collections have supported nulls.) In addition, prohibiting nulls offers opportunities for a more compact internal representation, faster access, and fewer special cases.

Since HashMap was already out in the wild when this was slowly getting discovered, it was too late to change it without breaking existing code but most recent implementations of those interfaces (e.g. ConcurrentHashMap) do not allow null anymore and the new collections for the factory methods are no exception.

(I thought another reason was that explicitly using null values was seen as a likely implementation error but I got that wrong. That was about to duplicate keys, which are illegal as well.)

So disallowing null had some technical reasons but it was also done to improve the robustness of the code using the created collections.

More on collection factory methods

The collections they return have two other interesting properties:

  • they are immutable
  • they randomize iteration order on each JVM launch

If you’d like to know more about the latter, there’s another StackOverflow questions with a great answer by Stuart Marks that clears that up.


When using the Flow API, are requested items added?

The question goes like this: In the Flow API, the subscriber must request items by calling request on the subscription. What's the contract: Is the given integer the new number of requested-but-not-yet-delivered elements or is it added to the current total? Does the subscriber have to keep track of the number of items?

The contract

The request method's Javadoc states (emphasis mine):

Adds the given number n of items to the current unfulfilled demand for this subscription. If n is less than or equal to zero, the Subscriber will receive an onError signal with an IllegalArgumentException argument. Otherwise, the Subscriber will receive up to n additional onNext invocations (or fewer if terminated).

So the answer is clearly yes, the subscriber needs to keep track of items. In fact, that’s the whole point of the mechanism.

Some background

The request method is meant to allow the subscriber to apply backpressure, informing upstream components that it is overloaded and "needs a break". It is hence the subscriber's (and only its) task to carefully vet when and how many new items to request. In that line it can not "reconsider" and lower the number of items to receive.

Furthermore, allowing the subscriber to set the total number would make the communication between publisher and subscriber “non-monotonic” in the sense that the number of totally requested items could suddenly lower (as it stands it can only increase). This is not only annoying in an abstract sense, it poses concrete consistency problems: The publisher could be in the process of delivering a few items when the subscriber suddenly drops the number of requested items to 1 — what now?


Loading resources in Java 9

How do you dynamically load e.g. an image (short of fiddling with relative paths)? Even more interestingly, how would one check if a class is available and make a decision dynamically (e.g. check if Jackson is available and, if so, use it for JSON serialization and if not use something else)?

That was the question and Mark Reinhold himself answered it, so have a look if this interests you.


Why does the unnamed module exist?

There was one question related to the module system that I found intriguing. Assuming you know about the unnamed and automatic modules, did you ever wonder why the unnamed module even exists? Why wouldn’t the module system simply create an automatic module for each JAR on the class path? That would save us from an entire special case we now have to learn about.

There are at least two reasons.

Checks on modules

Just as regular modules, automatic ones are suspect to certain examinations by the module system, e.g. not splitting packages. Since JARs on the class path can (and occasionally do) split packages, imposing that check on them would be backwards-incompatible and break a number of applications.

Reading JDK modules

The unnamed module can read all platform modules, whereas automatic modules can only read those that made it into the module graph. That means a JAR needing the java.desktop module (for example) will work from the class path but not from the module graph unless java.desktop also makes it into the graph (via a dependency or --add-modules).

At least that’s what the State of the Module system says:

After a module graph is resolved, therefore, an automatic module is made to read every other named module, whether automatic or explicit.

Resolution works on the declared dependencies and an automatic modules declares none, so all of its dependencies would have to be added manually. Considering that in the beginning most applications will run from the class path, that would be the case for all of their dependencies beyond java.base.


Depending on unmodularized projects

I had a little tweetstorm about what projects should do if they want to modularize but their dependencies didn’t yet. Here we go:

Small JPMS primer for declaring dependencies on unmodularized projects. First, read up on automatic modules. Then learn that unmodularized JARs can define their future module name in MANIFEST.MF. If present, it will become the automatic module’s name. That name is stable and good to rely on. Check your dependencies for that manifest entry and open issues in projects that don’t have it yet.

Regarding depending on JAR-name-based vs manifest-entry-based automatic names: If you’re not publishing your module (e.g. an application), either is fine. If you’re publishing your module (e.g. a library), depending on the JAR-name is problematic because it is likely going to be unstable (e.g. when the library gets properly modularized), so don’t publish your project with such dependencies. Yes, that might mean that you can not yet modularize your lib/framework. :(

Finally, get a good book on the module system: tiny.cc/j9ms :)



PS: Don’t forget to subscribe or recommend! :)

CodeFX Weekly

Whatever caught my interest throughout the week: libraries or tools I am using, what I’ve written or read that might interest you, fascinating questions on StackOverflow, conferences visits, odd tweets, anything really that connects to Java, software development, or writing.

Nicolai Parlog

Written by

Developer (mostly Java), blogger (http://codefx.org/), author (http://tiny.cc/jms), trainer (http://courses.codefx.org & https://www.youtube.com/c/codefx)

CodeFX Weekly

Whatever caught my interest throughout the week: libraries or tools I am using, what I’ve written or read that might interest you, fascinating questions on StackOverflow, conferences visits, odd tweets, anything really that connects to Java, software development, or writing.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade