Hello, Habr. As you remember, the
official release of Java 10 recently
occurred . Considering that almost everyone now uses mostly 8-ku, with the release of 10-ki, we are waiting for such goodies as modularity (entered 9-ku) and
local variable type inference . It sounds good, you can try to experiment with the transfer of some existing project to 10-ku.
About what kinds of pain are waiting for us, you can find out under the cut.
I
’ll just leave it here. I’m not aiming to present here all possible problems with the migration of java-projects from the 8th Java version to the 9th or 10th. I want to fix my little experience of first contact with new versions of Java somehow except in oral discussions with colleagues. Maybe he
will stop 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 lack of a theme for a part of the remaining elements. This was a small problem, since IDEA can specify a specific JVM to run.
')
1. Problems with the implementation of modularity
To begin with, let's make our application modular (it’s not for nothing that the JDK developers tried and
suffered over the Jigsaw project)?
1.1. Jdeps utility issues
To get the java module from the existing class hierarchy, we
will 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)
It seems that everything looks pretty simple at first glance.
1.1.1. Dependencies on external libraries no longer work without special spells.
Nothing is going to happen. If you want to build a modular application, then your dependencies must also be modular. Otherwise, it will simply not be possible to add them to module-info.java. You can add something more precisely - old-fashioned jar-files of libraries are considered as so-called “automatic modules”. However, the problem is that automatic modules cannot be used during assembly. Yes, in popular repositories so far there are very few libraries assembled as modules 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 an old-fashioned jar module. As a workaround
crutch , it was decided to simply take and
leave to unpack all the dependencies in one pile and make a single jar from which you can try to make a fashionable module-with-all-dependencies. The example on stackoverflow is
attached .
1.1.2. You will need to include significantly more dependencies than you really need.
Jdeps resolves dependencies recursively, and even dependencies that Maven / Gradle consider optional are required to be resolved. As a result, the dependency tree of even a small experimental project grows several times. A module with dependencies, which was decided to produce in paragraph (1.1.1),
drains the Internet floor becomes very heavy. An example of the presentation of this pain
here . Suddenly, it turns out that log4j requires AWT, OSGI, ZeroMQ, Kafka or something else like this.
1.1.3. Unexpected public [Roskomnadzor] utilities
It was found that in a situation where some classes are duplicated in different modules, jdeps fall out with the IllegalStateException stack path from somewhere in the depths of their code without any explanation of what went wrong. Only one small hook is the message “dependency on self” as an argument to the exception constructor. At some point it seemed to me that this disgrace
seemed to hint that everything (application classes, dependency classes, “optional” dependency classes) should be dumped into one jar file. And really - it worked.
1.2. Runtime issues
Even when everything was already miraculously gathered, modularly attached, and even a custom JRE image to it, it turned out that the most important surprises are still to come. That is, in runtime.
1.2.1. Now it is impossible to determine the path to the start file / directory
In a Java application, there is often a need to determine the path to the directory where
the pheasant sits a jar file from which the application was launched (for example, a jar file containing an entry point). This is usually necessary to understand where to look for application files — configuration, plug-ins, and so on. Such things usually lie in the neighborhood (which is quite logical). To determine this path, a spell of the form usually helps:
Main.getProtectionDomain().getCodeSource().getLocation().toURI()
What was my surprise when I discovered that in new versions of Java this spell no longer works and I see something like “jrt: //com.example.myapp” instead of the path to the file. I began to frantically google and overflow the stack. I tried to identify the module and somehow find out which files belong to it. Attempts to access the classpath also brought me to nothing, because modularly the application starts 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 minimize my experiment on moving to Java 10. No, of course, one could pass the directory path on the command line or force the user to run only from the correct directory. But for myself, I personally decided that I had enough for now.
1.2.2. Failure when trying to determine the file MIME type.
It turned out that the cherry on the cake was not one. For experiments with a ten, I installed it and configured it as the default JVM in the system. Therefore, surprises continued. Code like
final String mimeType = Files.probeContentType(itemInputFilePath);
began to cause null to be returned if the argument was a regular CSV file with the appropriate extension. “How so?” - I thought, “is it really impossible for the JVM to determine the MIME type for a regular CSV file?”. I did a little bang and found that the view call
Files.readAllLines("/etc/mime.types", Charset.defaultCharset())
completely impudently throws an unintelligible exception with a message like "malformed input".
Deeper in the reasons for this outrage, I did not understand yet. Just checked the file /etc/mime.types - the
cracks are not found, it seems, a normal text file. That is, the JVM could not read the lines from the text file.
1.2.3. Non-working mechanism Service Providers
Starting from version 9, Java also offers a new mechanism for Service Providers to replace the old one. The thing is not bad, it allows you to make plugins that are resolved at runtime. A new mechanism implies the declaration of an extension in module-info.java using the keywords “provides” and “with”. It would seem that it could be easier. In practice, this did not work, unlike the old mechanism (using the resources / META-INF / services / ... files). Understand what the problem is not enough time. However, the mark that does not take off without special spells, I probably will do it here.
2. Reflections on the implementation of local variable type inference
In the case of the local variable type inference, we have a new keyword "var" (variable, variable), which hints at the influence of Scala. However, personally it seemed to me strange, why they also did not add “val” (value, value). Instead, you have to write “final var”, which is read by the eyes as a “final variable” or even as a “constant variable” - and this is not only less concise, but also somehow slightly contradictory, in my opinion
, used to a good look.
Also found some pretty funny things. For example:
final var someStrings = new ArrayList<String>();
Here, the JVM “remembers” behind the name “someStrings” the type ArrayList <String>.
Sometimes, with full moon clever syntax highlighting, you can see a warning that the type of a constant is redundant, and in Java it is common to use interfaces in this place, such as List <String> or even Collection <String>. This warning from an extremely smart editor can be avoided by declaring a constant like this: This type is redundant, since it is common to use the minimally necessary interface to reduce potential connectivity (so that no one will begin to pull ArrayList-specific methods, for example). Local variable type inference violates this principle. This can be avoided in the following way:
final var someStrings = (List<String>) new ArrayList<>();
but it already looks somehow not very concise. This moment does not claim to be the title of some fatal flaw, but it demonstrates a slight patina of inconsistency.
3. Disappointing conclusions
Some of the problems voiced in this note emerged as early as version 9. Given that they have not been fixed so far, there is an unpleasant feeling that this whole mess will be in the 11th version and even further. I would not like to click into the world, but something else follows from this subjective feeling: Java, as a programming language, begins to experience its decline. Maybe I'm wrong, and maybe it is, and then to replace Java, you need to look for another tool.
UPDATE
Unfortunately, I am writing from memory, so some details are missing. I also remembered another great thing. This is an annotachka
sun.misc.Contended , which appeared in the 8th version and was safely cut out in subsequent ones. Despite her short life, she managed to get into one of the used
libraries , which also caused a lot of trouble. It is not in 10-ke and everything, do what you want, but for building the module it is required, since there is a link to the class. It all ended with the fact that I, remembering that the conversantmedia disruptor library is not a mandatory dependency, I simply replaced the required classes from it with empty ones in my source code. And this is one of the little things that did not have a number ...