Why do we need requires static in java-9 module system? [duplicate] - java

This question already has answers here:
What's the difference between requires and requires static in module declaration
(2 answers)
Does the Java 9 Module system support optional dependencies?
(1 answer)
Closed 3 years ago.
I started to learn jigsaw java-9 feature and read some articles/video.
I can't understand concept of optional dependencies(requires static)
quote from article:
When a module needs to be compiled against types from another module
but does not want to depend on it at run time, it can use a requires
static clause. If foo requires static bar, the module system behaves
different at compile and run time:
At compile time, bar must be present or there will be an error. During
compilation bar is readable by foo.
At run time, bar might be absent
and that will cause neither error nor warning. If it is present, it is
readable by foo.
So I want to know couple of things:
What the reason to make module dependable on another module during compile time but not in runtime? any examples? instruments like lombok?
Any analogs of optional dependencies in java prior java-9 ?
P.S.
I found one more explanation:
quote from article:
Sometimes we write code that references another module, but that users
of our library will never want to use.
For instance, we might write a utility function that pretty-prints our
internal state when another logging module is present. But, not every
consumer of our library will want this functionality, and they don’t
want to include an extra logging library.
In these cases, we want to use an optional dependency. By using the
requires static directive, we create a compile-time-only dependency:
module my.module {
requires static module.name;
}
But it is absolutely unclear for me. Could anyone explain it in a simple way?

There are a decent number of libraries out there where it only makes sense to have them at compile time. Mostly this deals with annotations that only exist to help during development (e.g. prevent bugs, reduce boilerplate). Some examples include:
java-annotations by JetBrains
spotbugs-annotations by SpotBugs (successor of FindBugs)
Project Lombok (as you mentioned)
jcip-annotations
These annotations tend to have a RetentionPolicy of SOURCE or CLASS, which means they aren't useful (or even available) at runtime. Why ship these dependencies with the rest of your application when you deploy? Without requires static you would be forced to include them when you deploy, otherwise your application would fail to start due to missing dependencies.
You would declare these dependencies as optional pre-Java 9 as well. Many Java projects of any significance use a build tool such as Maven or Gradle. In addition to those tools automatically building and testing your project, a large part of what they do is dependency management. I'm not familiar enough with Maven, but when using Gradle one would use:
dependencies {
compileOnly 'group.id:artifact-id:version'
}
To declare dependencies that are not needed at runtime.

If Dependent Module should be available at compile time but optional at rumtime, then such type of Dependency is called Optional Dependency. We can Specify optional dependency by using static keyword.
Note The static keyword is used to say that "This dependency check is mandatory at compile time and optional at runtime."
Eg.1
module moduleB {
requires moduleA;
}
moudleA should be available at the time of compilation & rumtime. it is not Optional Dependency.
Eg2.
module moduleB {
requires static moduleA;
}
At the time of compilation moduleA should be available, but at runtime it is optional ie, at runtime even moduleA is not avaiable JVM will execute code.

Related

How to test whether an optional module is present at runtime

The Java Platform Module System (JPMS) allows a module to declare an optional dependency using requires static in the module-info.java file:
module my.module {
requires static some.optional.module;
}
However, how should you check in your code whether the module is present at runtime?
The Project Jigsaw: Optional Modules page proposed:
public boolean isModulePresent(String mn);
But, it appears that was dropped because there is no such method in the latest Java releases. Neither am I able to find the #RequireOptionalModule annotation mentioned there in the current Java version.
This example on blog#CodeFX suggests a quite verbose method chain on StackWalker (which is explained here).
Edit: That example tries to find out if a module is available to the caller class, which is not necessary in my case.
Is there an easier way to perform this check, or can a simple Class.forName check suffice if the presence of a certain class indicates that a module is present?
To find out if some module is available at runtime, you can simply call ModuleLayer.findModule():
Optional<Module> module = ModuleLayer.boot().findModule("java.desktop");
System.out.println(module.isPresent());
Class.forName() should work too if you know the name of some specific class from the module. However, forName() throws ClassNotFoundException if a class is not found. This means you will have to surround it with try-catch to make it work. This is not very convenient.

How to create class that will only receive dependency in classpath at runtime?

I work for a company that distributes our product as a jar file, and I'm trying to write something that will be able to test past versions of these jars with various inputs. Ideally, I could then run the test framework like
java -jar testframework.jar -cp "version1.jar"
or
java -jar testframework.jar -cp "version2.jar"
and get different outputs. Since the methods that take in input are set in stone, I figured I could make the dependency on our product scope "provided" or "runtime" in maven, and then call input methods on whatever version of the jar was provided in the classpath. Something like this:
<dependency>
<groupId>com.ourCompany</groupid>
<artifactId>ourProduct</artifactId>
<scope>provided</scope>
</dependency>
and then in the main TestFramework class:
public static void main(String[] args) {
ProductClass.doSomething();
}
However, I'm getting a compilation error that the doSomething method doesn't exist. I imagine I'm misunderstanding exactly what "provided" and "runtime" mean with respect to maven dependencies, but I haven't been able to find any resources that explain my mistake. Does anyone know how I can do what I'm trying to do?
ProductClass definitely exists within ProductJar. It has no
problem importing the class, just calling the method doSomething. And
I'm getting that error when I use provided scope.
Because you are confirming that the JAR exists, the issue seems like with the version of the JAR file you are pointing to, so specify the <version>X</version> (in which the doSomething method exist) as well for the <dependency> and should solve the problem.
I'm misunderstanding exactly what "provided" and "runtime" mean with
respect to maven dependencies
provided and runtime scopes are completely different, they are for two different purposes.
provided scope means that the dependency is required during compile and test time (but, the dependency JAR will not be bundled as part of the JAR packaging, so the JAR should be available in the container classpath)
runtime scope means that the dependency is required only during execution of the program, not at compile time.
The dependencies always need to be available at compile time. Otherwise, how would the compiler be able to know if your code is valid or not? Check that the version you've declared in the dependency does indeed have the doSomething method you want to use. If not you will need to change the version to one that does have that method.

Can Gradle help solve jar hell in any way?

Java 8 here.
Say there is an old version of the widget libray, with Maven coordinates widgetmakers:widget:1.0.4, that has a class defined in it like so:
public class Widget {
private String meow;
// constructor, getters, setters, etc.
}
Years pass. The maintainers of this widget library decide that a Widget should never meow, rather, that it should in fact bark. And so a new release is made, with Maven coordinates widgetmakers:widget:2.0.0 and with Widget looking like:
public class Widget {
private Bark bark;
// constructor, getters, setters, etc.
}
So now I go to build my app, myapp. And, wanting to use the latest stable versions of all my dependencies, I declare my dependencies like so (inside of build.gradle):
dependencies {
compile (
,'org.slf4j:slf4j-api:1.7.20'
,'org.slf4j:slf4j-simple:1.7.20'
,'bupo:fizzbuzz:3.7.14'
,'commons-cli:commons-cli:1.2'
,'widgetmakers:widget:2.0.0'
)
}
Now let's say that this (fictional) fizzbuzz library has always depended on a 1.x version of the widget library, where Widget would meow.
So now, I'm specifying 2 versions of widget on my compile classpath:
widgetmakers:widget:1.0.4 which is pulled in by the fizzbuzz library, as a dependency of it; and
widgetmakers:widget:2.0.0 which I am referencing directly
So obviously, depending on which version of Widget gets classloaded first, we will either have a Widget#meow or a Widget#bark.
Does Gradle provide any facilities for helping me out here? Is there any way to pull in multiple versions of the same class, and configure fizzbuzz classes to use the old version of Widget, and my classes to use the new version? If not, the only solutions I can think of are:
I might be able to accomplish some kind of shading- and/or fatjar-based soltuion, where perhaps I pull in all my dependencies as packages under myapp/bin and then give them different version-prefixes. Admittedly I don't see a clear solution here, but am sure something is feasible (yet totally hacky/nasty). Or...
Carefully inspect my entire dependency graph and just make sure that all of my transitive dependencies don't conflict with each other. In this case for me, this means either submitting a pull-request to the fizzbuzz maintainers to upgrade it to the latest widget version, or, sadly, downgrading myapp to use the older widget version.
But Gradle (so far) has been magic for me. So I ask: is there any Gradle magic that can avail me here?
Don't know the specifics of Gradle, as I'm a Maven person, but this is more generic anyway. You basically have two options (and both are hacky):
ClassLoader magic. Somehow, you need to convince your build system to load two versions of the library (good luck with that), then at runtime, load the classes that use the old version with a ClassLoader that has the old version. I have done this, but it's a pain. (Tools like OSGI may take away some of this pain)
Package shading. Repackage the library A that uses the old version of library B, so that B is actually inside A, but with a B-specific package prefix. This is common practice, e.g. Spring ships its own version of asm. On the Maven side, the maven-shade-plugin does this, there probably is a Gradle equivalent. Or you can use ProGuard, the 800 pound gorilla of Jar manipulation.
Gradle will only set up the classpath with your dependencies, it doesn't provide its own runtime to encapsulate dependencies and its transitive dependencies. The version active at runtime will be the one according to the classloading rules, which I believe is the first jar in the classpath order to contain the class. OSGI provides runtime that can deal with situations like this and so will the upcoming module system.
EDIT: Bjorn is right in that it will try to resolve conflicts in different versions; it'll compile the classpath based on its strategies, so the order you put your dependencies in the file doesn't matter. However you still only get one class per classname, it won't resolve OP's issue
If you have different versions of a library with otherwise equal coordinates, Gradles conflict resolution mechanism comes into play.
The default resolution strategy is to use the newest requested version of the library. You will not get multiple versions of the same library in your dependendcy graph.
If you really need different versions of the same library at runtime you would have to either do some ClassLoader magic which definitely is possible or do some shading for one of the libraries or both.
Regarding conflict resolution, Gradle has built-in the newest strategy that is default and a fail strategy that fails if different versions are in the dependency graph and you have to explicitly resolve version conflicts in your build files.
Worse case is when the same class appears in multiple jars. This is more insidious - look at the metrics jars from Codahale and Dropwizard with incompatible versions of the same class in the two jars.
The gradle classpath-hell plugin can detect this horror.

Questions on compile- and run-time dependencies and their resolution?

I have the following questions:
In general: what is compile- and run-time dependencies
How the javac compiler does compile-time dependency resolution
How the VM does runtime-dependency resolution
I've skimmed the JLS and JVMS for "dependency" but haven't come up with anything.
In general: what is compile- and run-time dependencies
Compile-time dependency: You need the dependency in your CLASSPATH to compile your artifact. They are produced because you have some kind of "reference" to the dependency hardcoded in your code, such as calling new for some class, extending or implementing something (either directly or indirectly), or a method call using the direct reference.method() notation.
Run-time dependency: You need the dependency in your CLASSPATH to run your artifact. They are produced because you execute code that accesses the dependency (either in a hardcoded way or via reflection or whatever).
Taken from: Compile time vs Run time Dependency - Java however, a much detailed reference is available on Jenkov's website
How the javac compiler does compile-time dependency resolution
It looks at your classpath and sourcepath to find the necessary dependencies.
How the VM does runtime-dependency resolution
The JVM isn't exactly concerned with dependency resolution, AFAIK.
What the JVM does is to load classes from the classpath when they are needed.
For example, when your program (say class A) hits the first time:
// in class A
F x = new F(42);
the class F will be loaded.
If the class is not found, you get a ClassNotFoundException. If you changed the F class between compilation of A and execution, and dropped, for example, the F(int) constructor, then you get several different ugly exceptions.
"Dependency" is higher level of abstraction than the JVM is concerned with. There are many dependency management systems out there, like Maven and Gradle, but each of these runs on top of the JVM, not as part of it itself.
What you'll find in the Java platform documentation is "linking"; i.e., how the JVM can use the name of a Class or Interface to locate the byte code necessary to load additional pieces of your program. Java does this via a mechanism called the classpath. The classpath can be configured via environment variables, your build system, or at the command line as you run a program.

What causes java.lang.IncompatibleClassChangeError?

I'm packaging a Java library as a JAR, and it's throwing many java.lang.IncompatibleClassChangeErrors when I try to invoke methods from it. These errors seem to appear at random. What kinds of problems could be causing this error?
This means that you have made some incompatible binary changes to the library without recompiling the client code. Java Language Specification §13 details all such changes, most prominently, changing non-static non-private fields/methods to be static or vice versa.
Recompile the client code against the new library, and you should be good to go.
UPDATE: If you publish a public library, you should avoid making incompatible binary changes as much as possible to preserve what's known as "binary backward compatibility". Updating dependency jars alone ideally shouldn't break the application or the build. If you do have to break binary backward compatibility, it's recommended to increase the major version number (e.g. from 1.x.y to 2.0.0) before releasing the change.
Your newly packaged library is not backward binary compatible (BC) with old version. For this reason some of the library clients that are not recompiled may throw the exception.
This is a complete list of changes in Java library API that may cause clients built with an old version of the library to throw java.lang.IncompatibleClassChangeError if they run on a new one (i.e. breaking BC):
Non-final field become static,
Non-constant field become non-static,
Class become interface,
Interface become class,
if you add a new field to class/interface (or add new super-class/super-interface) then a static field from a super-interface of a client class C may hide an added field (with the same name) inherited from the super-class of C (very rare case).
Note: There are many other exceptions caused by other incompatible changes: NoSuchFieldError, NoSuchMethodError, IllegalAccessError, InstantiationError, VerifyError, NoClassDefFoundError and AbstractMethodError.
The better paper about BC is "Evolving Java-based APIs 2: Achieving API Binary Compatibility" written by Jim des Rivières.
There are also some automatic tools to detect such changes:
japi-compliance-checker
clirr
japitools
sigtest
japi-checker
Usage of japi-compliance-checker for your library:
japi-compliance-checker OLD.jar NEW.jar
Usage of clirr tool:
java -jar clirr-core-0.6-uber.jar -o OLD.jar -n NEW.jar
Good luck!
While these answers are all correct, resolving the problem is often more difficult. It's generally the result of two mildly different versions of the same dependency on the classpath, and is almost always caused by either a different superclass than was originally compiled against being on the classpath or some import of the transitive closure being different, but generally at class instantiation and constructor invocation. (After successful class loading and ctor invocation, you'll get NoSuchMethodException or whatnot.)
If the behavior appears random, it's likely the result of a multithreaded program classloading different transitive dependencies based on what code got hit first.
To resolve these, try launching the VM with -verbose as an argument, then look at the classes that were being loaded when the exception occurs. You should see some surprising information. For instance, having multiple copies of the same dependency and versions you never expected or would have accepted if you knew they were being included.
Resolving duplicate jars with Maven is best done with a combination of the maven-dependency-plugin and maven-enforcer-plugin under Maven (or SBT's Dependency Graph Plugin, then adding those jars to a section of your top-level POM or as imported dependency elements in SBT (to remove those dependencies).
Good luck!
I have also discovered that, when using JNI, invoking a Java method from C++, if you pass parameters to the invoked Java method in the wrong order, you will get this error when you attempt to use the parameters inside the called method (because they won't be the right type). I was initially taken aback that JNI does not do this checking for you as part of the class signature checking when you invoke the method, but I assume they don't do this kind of checking because you may be passing polymorphic parameters and they have to assume you know what you are doing.
Example C++ JNI Code:
void invokeFooDoSomething() {
jobject javaFred = FredFactory::getFred(); // Get a Fred jobject
jobject javaFoo = FooFactory::getFoo(); // Get a Foo jobject
jobject javaBar = FooFactory::getBar(); // Get a Bar jobject
jmethodID methodID = getDoSomethingMethodId() // Get the JNI Method ID
jniEnv->CallVoidMethod(javaFoo,
methodID,
javaFred, // Woops! I switched the Fred and Bar parameters!
javaBar);
// << Insert error handling code here to discover the JNI Exception >>
// ... This is where the IncompatibleClassChangeError will show up.
}
Example Java Code:
class Bar { ... }
class Fred {
public int size() { ... }
}
class Foo {
public void doSomething(Fred aFred, Bar anotherObject) {
if (name.size() > 0) { // Will throw a cryptic java.lang.IncompatibleClassChangeError
// Do some stuff...
}
}
}
I had the same issue, and later I figured out that I am running the application on Java version 1.4 while the application is compiled on version 6.
Actually, the reason was of having a duplicate library, one is located within the classpath and the other one is included inside a jar file that is located within the classpath.
In my case the error appeared when I added the com.nimbusds library in my application deployed on Websphere 8.5.
The below exception occurred:
Caused by: java.lang.IncompatibleClassChangeError: org.objectweb.asm.AnnotationVisitor
The solution was to exclude the asm jar from the library:
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>5.1</version>
<exclusions>
<exclusion>
<artifactId>asm</artifactId>
<groupId>org.ow2.asm</groupId>
</exclusion>
</exclusions>
</dependency>
Another situation where this error can appear is with Emma Code Coverage.
This happens when assigning an Object to an interface. I guess this has something to do with the Object being instrumented and not binary compatible anymore.
http://sourceforge.net/tracker/?func=detail&aid=3178921&group_id=177969&atid=883351
Fortunately this problem doesn't happen with Cobertura, so I've added cobertura-maven-plugin in my reporting plugins of my pom.xml
I've faced this issue while undeploying and redeploying a war with glassfish. My class structure was like this,
public interface A{
}
public class AImpl implements A{
}
and it was changed to
public abstract class A{
}
public class AImpl extends A{
}
After stopping and restarting the domain, it worked out fine.
I was using glassfish 3.1.43
All of the above - for whatever reason I was doing some big refactor and starting to get this. I renamed the package my interface was in and that cleared it. Hope that helps.
I have a web application that deploys perfectly fine on my local machine's tomcat(8.0.20). However, when I put it into the qa environment (tomcat - 8.0.20), it kept on giving me the IncompatibleClassChangeError and it was complaining that I was extending on an interface. This interface was changed to an abstract class. And I compiled the parent and child classes and still I kept on getting the same issue. Finally, I wanted to debug, so, I changed the version on the parent to x.0.1-SNAPSHOT and then compiled everything and now it is working. If someone is still hitting the problem after following the answers given here, please make sure the versions in your pom.xml are also correct. Change the versions to see if that works. If so, then fix the version problem.
My answer, I believe, will be Intellij specific.
I had rebuilt clean, even going as far as to manually delete the "out" and "target" dirs. Intellij has a "invalidate caches and restart", which sometimes clears odd errors. This time it didn't work. The dependency versions all looked correct in the project settings->modules menu.
The final answer was to manually delete my problem dependency from my local maven repo. An old version of bouncycastle was the culprit(I knew I had just changed versions and that would be the problem) and although the old version showed up no where in what was being built, it solved my problem. I was using intellij version 14 and then upgraded to 15 during this process.
In my case, I ran into this error this way. pom.xml of my project defined two dependencies A and B. And both A and B defined dependency on same artifact (call it C) but different versions of it (C.1 and C.2). When this happens, for each class in C maven can only select one version of the class from the two versions (while building an uber-jar). It will select the "nearest" version based on its dependency mediation rules and will output a warning "We have a duplicate class..." If a method/class signature changes between the versions, it can cause a java.lang.IncompatibleClassChangeError exception if the incorrect version is used at runtime.
Advanced: If A must use v1 of C and B must use v2 of C, then we must relocate C in A and B's poms to avoid class conflict (we have a duplicate class warning) when building the final project that depends on both A and B.
An additional cause of this issue, is if you have Instant Run enabled for Android Studio.
The fix
If you find you start getting this error, turn off Instant Run.
Android Studio main settings
Build, Execution, Deployment
Instant Run
Untick "Enable instant run..."
Why
Instant Run modifies a large number of things during development, to make it quicker to provide updates to your running App. Hence instant run. When it works, it is really useful. However, when an issue such as this strikes, the best thing to do is to turn off Instant Run until the next version of Android Studio releases.
Please check if your code doesnt consist of two module projects that have the same classes names and packages definition. For example this could happen if someone uses copy-paste to create new implementation of interface based on previous implementation.
If this is a record of possible occurences of this error then:
I just got this error on WAS (8.5.0.1), during the CXF (2.6.0) loading of the spring (3.1.1_release) configuration where a BeanInstantiationException rolled up a CXF ExtensionException, rolling up a IncompatibleClassChangeError. The following snippet shows the gist of the stack trace:
Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.apache.cxf.bus.spring.SpringBus]: Constructor threw exception; nested exception is org.apache.cxf.bus.extension.ExtensionException
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:162)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:76)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:990)
... 116 more
Caused by: org.apache.cxf.bus.extension.ExtensionException
at org.apache.cxf.bus.extension.Extension.tryClass(Extension.java:167)
at org.apache.cxf.bus.extension.Extension.getClassObject(Extension.java:179)
at org.apache.cxf.bus.extension.ExtensionManagerImpl.activateAllByType(ExtensionManagerImpl.java:138)
at org.apache.cxf.bus.extension.ExtensionManagerBus.<init>(ExtensionManagerBus.java:131)
[etc...]
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:147)
... 118 more
Caused by: java.lang.IncompatibleClassChangeError:
org.apache.neethi.AssertionBuilderFactory
at java.lang.ClassLoader.defineClassImpl(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:284)
[etc...]
at com.ibm.ws.classloader.CompoundClassLoader.loadClass(CompoundClassLoader.java:586)
at java.lang.ClassLoader.loadClass(ClassLoader.java:658)
at org.apache.cxf.bus.extension.Extension.tryClass(Extension.java:163)
... 128 more
In this case, the solution was to change the classpath order of the module in my war file. That is, open up the war application in the WAS console under and select the client module(s). In the module configuration, set the class-loading to be "parent last".
This is found in the WAS console:
Applicatoins -> Application Types -> WebSphere Enterprise Applications
Click link representing your application (war)
Click "Manage Modules" under "Modules" section
Click link for the underlying module(s)
Change "Class loader order" to be "(parent last)".
Documenting another scenario after burning way too much time.
Make sure you don't have a dependency jar that has a class with an EJB annotation on it.
We had a common jar file that had an #local annotation. That class was later moved out of that common project and into our main ejb jar project. Our ejb jar and our common jar are both bundled within an ear. The version of our common jar dependency was not updated. Thus 2 classes trying to be something with incompatible changes.
For some reason the same exception is also thrown when using JNI and passing the jclass argument instead of the jobject when calling a Call*Method().
This is similar to the answer from Ogre Psalm33.
void example(JNIEnv *env, jobject inJavaList) {
jclass class_List = env->FindClass("java/util/List");
jmethodID method_size = env->GetMethodID(class_List, "size", "()I");
long size = env->CallIntMethod(class_List, method_size); // should be passing 'inJavaList' instead of 'class_List'
std::cout << "LIST SIZE " << size << std::endl;
}
I know it is a bit late to answer this question 5 years after being asked but this is one of the top hits when searching for java.lang.IncompatibleClassChangeError so I wanted to document this special case.
Adding my 2 cents .If you are using scala and sbt and scala-logging as dependency ;then this can happen because scala-logging's earlier version had the name scala-logging-api.So;essentially the dependency resolutions do not happen because of different names leading to runtime errors while launching the scala application.
I got this error because I had an abstract base class which promised that it implements a certain interface, but I had forgotten to add the implementations of the interface methods, and then I created a non-abstract (concrete) bytecode-generated class which extended the abstract class, without providing implementations for those methods, either.
When I tried to create an instance the bytecode-generated class, the JVM complained with java.lang.IncompatibleClassChangeError.
Luckily, the exception has a "message" member which provides more detailed information as to what went wrong. In my case the message clearly said that the particular class was supposed to implement the particular interface, but it did not actually implement it.
If you came from android development. Then give a try of rebuild option might be fix for you.
In my case:
I have a project containing a few modules, including app, test, integrationTest
I created OneElementCache in app module.
Then, I created a file Cache in test module, the file contains some helpers for creating OneElementCache in tests.
Until now, everything works perfectly (both test and integrationTest passes).
After that, I created a file Cache in app module.
Got while running integrationTest:
Caused by: java.lang.IncompatibleClassChangeError:
class app.cache.CacheImpl can not implement app.cache.Cache, because it is not an interface (app.cache.Cache is in unnamed module of loader 'app')
The reason was a conflict in naming in different modules (app/test). Changing the filename in test did the job.

Categories

Resources