Java 10 migration notes
Hello, Habr. As you recall, the official release of Java 10 has recently taken place . Considering that almost everyone now uses mainly 8-ku, with the release of 10, we are waiting for such goodies as modularity (included in 9-ku) and local variable type inference . Sounds good, you can try experimenting with transferring some existing project to 10-ku.

You can find out about what types of pain are waiting for us under kat.
I’ll just leave it here. I’m not aiming to state here all the possible problems with migrating java projects from the 8th version of Java to the 9th or 10th. I would like to fix my little experience of the first contact with new versions of Java somehow other than in oral discussions with colleagues. Maybe he will stop someone, someone will be useful.

To begin with, IDEA version 2018.1 refused to work on JVM versions 9 and 10 and on 2 different machines. It looked like the absence of a part of the GUI elements and the absence of a theme for a part of the remaining elements. This was a small issue since IDEA can specify a specific JVM to run.
To get started, let's make our application modular (it was not for nothing that the JDK developers tried and suffered over the Jigsaw project)?
To get the java module from the existing class hierarchy, we need :

Everything seems to be quite simple at first glance.
Nothing is just going to get together. If you want to build a modular application, then your dependencies must also be modular. Otherwise, you simply cannot add them to module-info.java. More precisely, you can add - old-fashioned jar-files of libraries are considered as the so-called "automatic modules". However, the problem is that automatic modules cannot be used during assembly. Yes, there are very few libraries compiled as modules in popular repositories so far and you simply cannot use them. Nobody cares.
This is where the jdeps utility was needed, which can parse the jar file and generate module-info.java for it to make a module from an old-fashioned jar. As acrutch workaround, it was decided to just go and leaveunpack all the dependencies in one heap and make a single jar from which you can try to make a fashionable module-with-all-dependencies. An example on stackoverflow is attached .
Jdeps resolves dependencies recursively, and requires the mandatory resolution of even dependencies, which Maven / Gradle considers optional. As a result, the dependency tree of even a small experimental project grows several times. A module with dependencies, which it was decided to manufacture in paragraph (1.1.1)deflates the Internet floor becomes very heavy. An example of the presentation of this pain here . Suddenly, it appears that log4j requires AWT, OSGI, ZeroMQ, Kafka, or something like that.
It turned out that in a situation where some classes are duplicated in different modules, jdeps falls out with the IllegalStateException stackrace from somewhere in the depths of its code without any explanation what went wrong. Only one small clue is the message "dependency on self" as an argument to the constructor of the exception. At some point, it seemed to me that this disgrace seemed to hint at the fact that everything (application classes, dependency classes, classes of “optional” dependencies) should be dumped into one jar file. And really - it worked.
Even when everything had already miraculously assembled, the modular application and even the custom JRE image for it, it turned out that the most important surprises were yet to come. That is, in runtime.
In Java applications, there is often a need to determine the path to the directory wherethe pheasant sits, the jar file from which the application was launched (for example, a jar file containing the entry point). This is usually necessary to understand where to look for application files - configuration, plugins, etc. Such things, as a rule, lie next door (which is very logical). A spell of the form usually helps to determine this path:
Imagine my surprise when I discovered that in newer versions of Java this spell no longer works and instead of the file path I see something like “jrt: //com.example.myapp”. I started feverishly googling and overflowing the stack. I tried to determine the module and somehow figure out which files belong to it. Attempts to access the classpath also led me to nothing, since the application is modularly launched with the so-called module path instead of the old-fashioned classpath. It was the finish. It was at that moment that I decided to curtail my experiment on moving to Java 10. No, of course, it would be possible to transfer the path to the directory on the command line or force the user to run only from the correct directory. But for myself I personally decided that for now I have enough.
It turned out that the cherry on the cake was not alone. For experiments with the top ten, I installed it and configured it as the default JVM in the system. Therefore, the surprises continued. Code like
began to lead to the fact that null was returned if the argument was a regular CSV file with the corresponding extension. “How so?” I thought, “Is the JVM unable to determine the MIME type for a regular CSV file?” I was a little beggar and found that a call of the kind
throws a tiny exception with a message like "malformed input".
Deeper in the reasons for this disgrace I did not begin yet. I just checked the /etc/mime.types file - thekrakozyabras were not found, it seems like a normal text file. That is, the JVM was unable to read lines from a text file.
Starting with version 9, Java also offers a new Service Providers mechanism to replace the old one. The thing is not bad, it allows you to make plugins that are allowed in runtime. The new mechanism involves declaring the extension in module-info.java using the keywords “provides” and “with”. It would seem that could be easier. In practice, this did not work, unlike the old mechanism (using resources / META-INF / services / ... files). Understand what the problem of time was not enough. However, a mark that without special spells does not take off, I will probably do it here.
In the case of local variable type inference, we have a new keyword “var” (variable, variable), which hints at the influence of Scala. However, it seemed strange to me personally why they did not add “val” (value) either. Instead, you have to write “final var”, which is read with your eyes as “final variable” or even as “constant variable” - and this is not only less concise, but also somehow slightly contradictory, in my opinion, used to a good look.
Some pretty funny things also showed up. For instance:
Here the JVM "remembers" the name "someStrings" type ArrayList. Sometimes, with the full moon smart syntax highlighting, you can see a warning that the type of constant is redundant, and in Java it is customary to use interfaces in this place, for example List or even Collection. This warning from a very smart editor can be avoided by declaring a constant like this: This type is redundant, since it is customary to use the minimally necessary interface to reduce potential connectivity (so that no one later pulls for ArrayList-specific methods, for example). Local variable type inference violates this principle. This can be avoided in the following way:
but it already looks somehow not very concise. This moment does not pretend to be the title of some fatal flaw, but demonstrates a slight touch of inconsistency.
Some of the problems voiced in this note arose in the 9th version. Given that they have not been fixed until now, there is an unpleasant feeling that this whole mess will be in the 11th version and even further. I would not want to click, but something else follows from this subjective sensation: Java, as a programming language, begins to experience its decline. Maybe I'm wrong, but maybe it is, and then to replace Java you need to look for another tool.
Unfortunately, I write from memory, so some details are missing. I also remembered another wonderful thing. This is annotashka sun.misc.Contended , which appeared in the 8th version and was safely drunk in subsequent ones. Despite her short life, she managed to get into one of the libraries used , which also caused a lot of trouble. It is not in 10-ke and that's it, do what you want, but to build a module it is required, since there is a link to the class. It all ended up with the fact that, remembering that the conversantmedia disruptor library is not a required dependency, I simply replaced the required classes from it with empty ones in my sources. And this is one of the little things that did not have a number ...

You can find out about what types of pain are waiting for us under kat.
I

To begin with, IDEA version 2018.1 refused to work on JVM versions 9 and 10 and on 2 different machines. It looked like the absence of a part of the GUI elements and the absence of a theme for a part of the remaining elements. This was a small issue since IDEA can specify a specific JVM to run.
1. Problems with the implementation of modularity
To get started, let's make our application modular (it was not for nothing that the JDK developers tried and suffered over the Jigsaw project)?
1.1. Problems with jdeps utility
To get the java module from the existing class hierarchy, we need :
- write 1 or more module-info.java files with the names of modules, dependencies and exported packages in them
- add arguments to the compiler (--module-path and others)

Everything seems to be quite simple at first glance.
1.1.1. External library dependencies no longer work without special spells
Nothing is just going to get together. If you want to build a modular application, then your dependencies must also be modular. Otherwise, you simply cannot add them to module-info.java. More precisely, you can add - old-fashioned jar-files of libraries are considered as the so-called "automatic modules". However, the problem is that automatic modules cannot be used during assembly. Yes, there are very few libraries compiled as modules in popular repositories so far and you simply cannot use them. Nobody cares.
This is where the jdeps utility was needed, which can parse the jar file and generate module-info.java for it to make a module from an old-fashioned jar. As a
1.1.2. You will need to include significantly more dependencies than is actually required
Jdeps resolves dependencies recursively, and requires the mandatory resolution of even dependencies, which Maven / Gradle considers optional. As a result, the dependency tree of even a small experimental project grows several times. A module with dependencies, which it was decided to manufacture in paragraph (1.1.1)
1.1.3. Unexpected public [Roskomnadzor] utility
It turned out that in a situation where some classes are duplicated in different modules, jdeps falls out with the IllegalStateException stackrace from somewhere in the depths of its code without any explanation what went wrong. Only one small clue is the message "dependency on self" as an argument to the constructor of the exception. At some point, it seemed to me that this disgrace seemed to hint at the fact that everything (application classes, dependency classes, classes of “optional” dependencies) should be dumped into one jar file. And really - it worked.
1.2. Problems with runtime
Even when everything had already miraculously assembled, the modular application and even the custom JRE image for it, it turned out that the most important surprises were yet to come. That is, in runtime.
1.2.1. Now it is impossible to determine the path to the start file / directory
In Java applications, there is often a need to determine the path to the directory where
Main.getProtectionDomain().getCodeSource().getLocation().toURI()
Imagine my surprise when I discovered that in newer versions of Java this spell no longer works and instead of the file path I see something like “jrt: //com.example.myapp”. I started feverishly googling and overflowing the stack. I tried to determine the module and somehow figure out which files belong to it. Attempts to access the classpath also led me to nothing, since the application is modularly launched with the so-called module path instead of the old-fashioned classpath. It was the finish. It was at that moment that I decided to curtail my experiment on moving to Java 10. No, of course, it would be possible to transfer the path to the directory on the command line or force the user to run only from the correct directory. But for myself I personally decided that for now I have enough.
1.2.2. Failure when trying to determine the MIME type of the file.
It turned out that the cherry on the cake was not alone. For experiments with the top ten, I installed it and configured it as the default JVM in the system. Therefore, the surprises continued. Code like
final String mimeType = Files.probeContentType(itemInputFilePath);
began to lead to the fact that null was returned if the argument was a regular CSV file with the corresponding extension. “How so?” I thought, “Is the JVM unable to determine the MIME type for a regular CSV file?” I was a little beggar and found that a call of the kind
Files.readAllLines("/etc/mime.types", Charset.defaultCharset())
throws a tiny exception with a message like "malformed input".
Deeper in the reasons for this disgrace I did not begin yet. I just checked the /etc/mime.types file - the
1.2.3. Broken new Service Providers mechanism
Starting with version 9, Java also offers a new Service Providers mechanism to replace the old one. The thing is not bad, it allows you to make plugins that are allowed in runtime. The new mechanism involves declaring the extension in module-info.java using the keywords “provides” and “with”. It would seem that could be easier. In practice, this did not work, unlike the old mechanism (using resources / META-INF / services / ... files). Understand what the problem of time was not enough. However, a mark that without special spells does not take off, I will probably do it here.
2. Reflections on the implementation of local variable type inference
In the case of local variable type inference, we have a new keyword “var” (variable, variable), which hints at the influence of Scala. However, it seemed strange to me personally why they did not add “val” (value) either. Instead, you have to write “final var”, which is read with your eyes as “final variable” or even as “constant variable” - and this is not only less concise, but also somehow slightly contradictory, in my opinion
Some pretty funny things also showed up. For instance:
final var someStrings = new ArrayList();
Here the JVM "remembers" the name "someStrings" type ArrayList
final var someStrings = (List) new ArrayList<>();
but it already looks somehow not very concise. This moment does not pretend to be the title of some fatal flaw, but demonstrates a slight touch of inconsistency.
3. Disappointing conclusions
Some of the problems voiced in this note arose in the 9th version. Given that they have not been fixed until now, there is an unpleasant feeling that this whole mess will be in the 11th version and even further. I would not want to click, but something else follows from this subjective sensation: Java, as a programming language, begins to experience its decline. Maybe I'm wrong, but maybe it is, and then to replace Java you need to look for another tool.
UPDATE
Unfortunately, I write from memory, so some details are missing. I also remembered another wonderful thing. This is annotashka sun.misc.Contended , which appeared in the 8th version and was safely drunk in subsequent ones. Despite her short life, she managed to get into one of the libraries used , which also caused a lot of trouble. It is not in 10-ke and that's it, do what you want, but to build a module it is required, since there is a link to the class. It all ended up with the fact that, remembering that the conversantmedia disruptor library is not a required dependency, I simply replaced the required classes from it with empty ones in my sources. And this is one of the little things that did not have a number ...