Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Class not found error when using equalsverifier in a Java module #746

Open
armandino opened this issue Dec 19, 2022 · 18 comments
Open

Class not found error when using equalsverifier in a Java module #746

armandino opened this issue Dec 19, 2022 · 18 comments

Comments

@armandino
Copy link

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 nl.jqno.equalsverifier@3.12.3/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 nl.jqno.equalsverifier@3.12.3/nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues.addUncommonClasses(JavaApiPrefabValues.java:327)
	at nl.jqno.equalsverifier@3.12.3/nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues.addJavaClasses(JavaApiPrefabValues.java:103)
	at nl.jqno.equalsverifier@3.12.3/nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues.build(JavaApiPrefabValues.java:95)
	at nl.jqno.equalsverifier@3.12.3/nl.jqno.equalsverifier.internal.util.Configuration.build(Configuration.java:87)
	at nl.jqno.equalsverifier@3.12.3/nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.buildConfig(SingleTypeEqualsVerifierApi.java:389)
	at nl.jqno.equalsverifier@3.12.3/nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.performVerification(SingleTypeEqualsVerifierApi.java:375)
	at nl.jqno.equalsverifier@3.12.3/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).

@jqno
Copy link
Owner

jqno commented Dec 19, 2022

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 jqno added the accepted label Dec 19, 2022
@armandino
Copy link
Author

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...

@jqno
Copy link
Owner

jqno commented Dec 20, 2022

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 😉

@scordio
Copy link
Contributor

scordio commented Dec 20, 2022

@sormuras has a nice blog post on this subject.

@jqno
Copy link
Owner

jqno commented Dec 20, 2022

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

@sormuras
Copy link

Happy to help, if needed.

@armandino
Copy link
Author

@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.

@jqno
Copy link
Owner

jqno commented Dec 21, 2022

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!

@armandino
Copy link
Author

Just did. GitHub is ahead of me :)

@jqno
Copy link
Owner

jqno commented Sep 22, 2023

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.

@armandino
Copy link
Author

@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.

@jqno
Copy link
Owner

jqno commented Sep 26, 2023

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.

@JiangHongTiao
Copy link

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

@jqno
Copy link
Owner

jqno commented Oct 17, 2023

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
Copy link
Owner

jqno commented Oct 2, 2024

I finally have a solution for this.

The issue with java.sql.Date is solved by loading it using reflection, as discussed earlier. I probably should've done that a long time ago, sorry about that...

The error that pops up when this change is made, the ClassNotFoundException on sun.reflect.ReflectionFactory was much harder. The thing that derailed me for a long time was that it happens in your reproducer project, but that loading EqualsVerifier in a modules-enabled project seemed to work just fine.

It turns out that your project runs EqualsVerifier as a regular Java application, and unit tests (whether using Maven Surefire or IntelliJ) are...different. Somehow. It took me a while to figure that out, and I ended up opening a new ticket with Objenesis, and asking on StackOverflow 😅.

The TL;DR is, if I add requires jdk.unsupported; to the module-info.java in your reproducer, it works (assuming I use the version where java.sql.Date is added using reflection). The reason why this isn't needed in a regular unit test, is that Maven Surefire (and, apparently, IntelliJ) run on the classpath, but load EqualsVerifier on the modulepath. In this situation, for some reason, you have full access to a bunch of built-in modules, including jdk.unsupported and java.sql, without having to require them in module-info.java.

So. I just released EqualsVerifier 3.17.1, which loads java.sql.Date (and a few other) via reflection.
To make it work in the reproducer, you have to add requires jdk.unsupported; to the module-info.java. But when running EqualsVerifier from a unit test, neither of these things are needed.

I'm really sorry I took two years to resolve this issue. I know that you've moved on in the mean time, so you're probably not waiting for this one anymore, but still 😅.

@jqno
Copy link
Owner

jqno commented Oct 2, 2024

BTW, I've been looking at your Instancio project, and it looks very very nice. I'm tempted to try and see if I can use it for object instantiation in EqualsVerifier now, so I can get rid of my own messy code 😅

@armandino
Copy link
Author

Hey @jqno, no worries at all and thank you for looking into this! It was a tricky one. I think we haven't really come around to modules yet. At least personally, I'm still getting used to them. Either way, I guess you'll be happy to finally close this issue :D

BTW, I've been looking at your Instancio project, and it looks very very nice. I'm tempted to try and see if I can use it for object instantiation in EqualsVerifier now, so I can get rid of my own messy code 😅

Thanks very much, it means a lot! I've been using EV for years and it would be amazing if my little project could contribute to it 😅 If it fits your needs and you decide to use it, please let me know if I can help in any way, or if you have any feedback.

@jqno
Copy link
Owner

jqno commented Oct 3, 2024

I have a couple of big projects for EqualsVerifier that I want to do. One of them is make it properly modular, so as part of that I really wanted to get this ticket resolved. Another is to re-do the instantiation logic to make it less complicated and more robust, which is where Instancio might come in :). I haven't decided yet which I want to do first, or when I actually have time to do one of them...but thanks for the offer, I'll definitely keep it in mind!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants