equalsverifier icon indicating copy to clipboard operation
equalsverifier copied to clipboard

Class not found error when using equalsverifier in a Java module

Open armandino opened this issue 1 year ago • 14 comments

Describe the bug The error occurs when a project does not declare requires java.sql in its module-info.java. equalsverifier references classes from that package leading to the error.

To Reproduce Minimal sample: https://github.com/armandino/equalsverifier-java9-module-bug

Error message

Exception in thread "main" java.lang.AssertionError: EqualsVerifier found a problem in class org.example.domain.Person.
-> java/sql/Date

For more information, go to: https://www.jqno.nl/equalsverifier/errormessages
(EqualsVerifier null, JDK 19.0.1 on Linux)
	at [email protected]/nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.verify(SingleTypeEqualsVerifierApi.java:316)
	at user/org.example.domain.Person.main(Person.java:23)
Caused by: java.lang.NoClassDefFoundError: java/sql/Date
	at [email protected]/nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues.addUncommonClasses(JavaApiPrefabValues.java:327)
	at [email protected]/nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues.addJavaClasses(JavaApiPrefabValues.java:103)
	at [email protected]/nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues.build(JavaApiPrefabValues.java:95)
	at [email protected]/nl.jqno.equalsverifier.internal.util.Configuration.build(Configuration.java:87)
	at [email protected]/nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.buildConfig(SingleTypeEqualsVerifierApi.java:389)
	at [email protected]/nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.performVerification(SingleTypeEqualsVerifierApi.java:375)
	at [email protected]/nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.verify(SingleTypeEqualsVerifierApi.java:312)
	... 1 more
Caused by: java.lang.ClassNotFoundException: java.sql.Date
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
	... 8 more

Expected behavior If an application does not require java.sql equalsverifier should still work.

Version 3.12.3 (no deps)

Additional context Error occurs in JavaApiPrefabValues that imports java.sql.* classes. Loading them via Class.forName might be a potential solution (had to do something similar myself in another project).

armandino avatar Dec 19 '22 03:12 armandino

Thanks for letting me know, and for creating the sample project. I was able to reproduce this quite quickly.

I agree with you that things should just work when using modules. Indeed, I can load the java.sql classes using reflection, as you suggest. The subsequent error (ClassNotFoundException sun.reflect.ReflectionFactory) is a bit harder, I'll have to research that some more.

Out of curiosity, are you running your unit tests on the module path? If so, how do you do that?

jqno avatar Dec 19 '22 11:12 jqno

I don't use modules myself and my experience with them is limited. However I'm also working on a testing library and I'd like it to support modules for users who do use them. I discovered this issue in my project. I then tested it with equalsverifier as it also supports a lot of java types, and this has led to this bug report.

Ideally, I'd like to have a Maven test module (that is also a Java module) to verify the library works properly on the module path. However I haven't been able to get that to work in a multi-module Maven build as I'm using Automatic-Module-Names. I think this would require adding module-info.java files but I could be wrong...

armandino avatar Dec 19 '22 21:12 armandino

Yeah, I'd like to have some kind of test for this as well that I can run from Maven. Perhaps I could add a proper module-info.java. But first let's see if I can find a solution for this bug. Please let me know if you figure something out though :wink:

jqno avatar Dec 20 '22 08:12 jqno

@sormuras has a nice blog post on this subject.

scordio avatar Dec 20 '22 09:12 scordio

Thanks, I think I've read that when I first looked into modules... will take another look when I pick this up.

jqno avatar Dec 20 '22 11:12 jqno

Happy to help, if needed.

sormuras avatar Dec 20 '22 11:12 sormuras

@jqno I just noticed that if you run the sample from the command line, the error does not occur. It works even without requires java.sql:

mvn package exec:java -Dexec.mainClass=org.example.domain.Person

Regarding the second error (ClassNotFoundException sun.reflect.ReflectionFactory), I was able to reproduce it without equalsverifier by using Objenesis directly:

Objenesis objenesis = new ObjenesisStd();
ObjectInstantiator<Person> result = objenesis.getInstantiatorOf(Person.class);

This also works using mvn exec and fails in IntelliJ. I created a separate sample to submit an Objenesis bug report, but at this point, I'm not even sure which behaviour is correct, IntelliJ or mvn exec.

armandino avatar Dec 20 '22 17:12 armandino

I noticed that too, I think the Maven exec plugin just puts everything on the classpath by default instead of on the modulepath, and that IntelliJ is a bit smarter about this. But I'll have to investigate more :)

Did you submit something with Objenesis? If so, can you share the link? I'd like to follow that as well!

jqno avatar Dec 21 '22 07:12 jqno

Just did. GitHub is ahead of me :)

armandino avatar Dec 21 '22 07:12 armandino

I was going though old tickets, and tried this one again. It's still blocked on the Objenesis issue, unfortunately. I've added a label to make this clearer at a glance from the issues list.

jqno avatar Sep 22 '23 11:09 jqno

@jqno I ended up removing it as a dependency and using Unsafe and ReflectionFactory directly, instead of using Objenesis. It was a fairly small change, and the benefit is that it allows checking if sun.* classes are available at runtime and avoid using them if not. This also removes one transitive dependency for users.

Not sure if this approach will work for EV. It could be a breaking change since Objenesis provides some additional strategies for instantiating classes, however those could be handled as they arise. Might be worth investigating.

armandino avatar Sep 23 '23 20:09 armandino

Thanks for the response. That could be a good workaround for individual cases, but it's not something I'd like to include in EqualsVerifier itself. Good to know about this option though, so I can refer others to it if they run into this.

jqno avatar Sep 26 '23 06:09 jqno

I think I'm getting similar issue. It's not java.sql however When I'm trying to test object that inherits from Throwable:

java.lang.AssertionError: EqualsVerifier found a problem in class church.i18n.processing.exception.ProcessingException.
-> Unable to make field private transient java.lang.Object java.lang.Throwable.backtrace accessible: module java.base does not "opens java.lang" to unnamed module @55fe41ea

I'm trying to migrate into Java17. The project it OpenSource: https://bitbucket.org/i18n_church/church.i18n.processing.exception

JiangHongTiao avatar Oct 14 '23 19:10 JiangHongTiao

That's actually a different issue. OP is making their own modules, while you're extending a class form an existing, JDK-internal module.

Unfortunately, in this case you'll have to adjust your build scripts to actually open the java.lang module. Here's a StackOverflow answer that should get you on your way: https://stackoverflow.com/questions/74006627/module-java-base-does-not-opens-java-lang-java-17-0-4-1

jqno avatar Oct 17 '23 07:10 jqno