ClassNotFoundException in embedded Jetty when using Module system - java

I use an embedded Jetty (11.0.13) server with Jersey (3.1.0) that provides a simple REST interface which returns JSON objects. The JSON objects are serialized using Jackson.
The setup works fine as long as I don´t use Java´s module system.
But when I add the module-info.java file (see below), I get the following error as soon as I call the service.
WARNING: The following warnings have been detected: WARNING: Unknown HK2 failure detected:
MultiException stack 1 of 2
java.lang.NoClassDefFoundError: jakarta/xml/bind/annotation/XmlElement
at com.fasterxml.jackson.module.jakarta.xmlbind.JakartaXmlBindAnnotationIntrospector.<init>(JakartaXmlBindAnnotationIntrospector.java:137)
...
Caused by: java.lang.ClassNotFoundException: jakarta.xml.bind.annotation.XmlElement
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:520)
... 83 more
MultiException stack 2 of 2
java.lang.IllegalStateException: Unable to perform operation: post construct on org.glassfish.jersey.jackson.internal.DefaultJacksonJaxbJsonProvider
at org.jvnet.hk2.internal.ClazzCreator.create(ClazzCreator.java:429)
at org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:466)
...
To make it work, I have to add the JAX-B-API to the pom.xml and to the module-info.java.
The error only occurs when using Java modules. When I simply delete the module-info.java file, everythink works fine even without the JAX-B dependency.
This is the point where I am really confused. Why do I need the JAX-B dependency when I use the module system, but not when I don´t use it? And why does the ClassNotFoundException even occur? Shouldn´t warn the module system about missing dependencies on startup?
I hope someone can explain that. It took me days to make it work.
This is the setup that produces the issue:
pom.xml
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>11.0.13</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>11.0.13</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
</project>
Main.java
public class Main {
public static void main(String[] args) throws Exception {
Server server = new Server(8080);
server.setStopAtShutdown(true);
ServletContextHandler context = new ServletContextHandler(server, "/");
ServletHolder servletHolder = context.addServlet(ServletContainer.class, "/*");
servletHolder.setInitParameter("jersey.config.server.provider.packages", "com.example.demo");
servletHolder.setInitParameter("jersey.config.server.wadl.disableWadl", "true");
server.start();
}
}
DemoResource.java
#Path("/hello")
public class DemoResource {
#GET
#Produces("application/json")
public HelloDto hello() {
return new HelloDto("Hello, World!");
}
public record HelloDto(String value) {
#JsonGetter("value")
public String value() {
return this.value;
}
}
}
module-info.java
module demo {
requires org.eclipse.jetty.server;
requires org.eclipse.jetty.servlet;
requires jersey.container.servlet.core;
requires jakarta.ws.rs;
requires com.fasterxml.jackson.annotation;
}

This is the standard JVM behavior of classpath (old school Java) and modulepath (new school Java Platform Module System, aka JPMS).
Once you have a module-info.class you have a modulepath active, and all of the access rules it has.
Your runtime can have both at the same time, and this is quite normal.
Don't rely on old school classpath to get around bad code and bad behavior, use JPMS and module-info.class and you'll know what the developers of those projects jars intend for you to use (you won't be allowed to use internal classes for example, as those are highly volatile and can change at a moments notice).
jakarta.xml.bind is required by HK2 to operate, so you have to declare it in your build dependencies to just compile, and then your module-info.java to be able to access it.
Check the other answers here on Stackoverflow for advice on how to use module-info.java properly (there's far more to it than just requires <module>).

Related

resilience4j-spring-boot-2 annotations (#Retry, #CircuitBreaker...) are completely ignored

I spent a whole day trying to find why this does not work so I think it might be useful if I share the question and the answer.
The Resilience4j library provides an elegant annotation-based solution from Spring Boot 2. All you need to do is just annotate a method (or a class) with one of the provided annotations, such as #CircuitBreaker, #Retry, #RateLimiter, #Bulkhead, #Thread and the appropriate resilience pattern is automagically added.
I added the expected dependency to the Maven pom.xml:
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>${resilience4j.version}</version>
</dependency>
Now the compiler is happy, so I can add the annotations:
...
import org.springframework.stereotype.Service;
import io.github.resilience4j.retry.annotation.Retry;
...
#Service
public class MyService {
...
#Retry(name = "get-response")
public MyResponse getResponse(MyRequest request) {
...
}
}
The program compiles, runs, however the annotations are completely ignored.
According to the resilience4j-spring-boot2 documentation:
The module expects that spring-boot-starter-actuator and spring-boot-starter-aop are already provided at runtime.
So the whole trick is to add also the missing dependencies to the Maven pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

org.apache.cxf.interceptor.Fault: com/itextpdf/text/Document NoClassDefFoundError

I keep getting the following error trying to use a PDF lib and not having any luck. I am using spring and maven. I have tried pdfbox and itextpdf.
I get the Following error with either one ( obv the class part is different )
org.apache.cxf.interceptor.Fault: com/itextpdf/text/Document
Caused by: java.lang.NoClassDefFoundError: com/itextpdf/text/Document
pom
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.14</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
Class
import com.itextpdf.text.Document;
public enum PDFController {
INSTANCE;
PDFController() {
}
public void testPDF() {
// Error happens here
Document doc = new Document();
}
}
It appears Maven is adding the correct jars. So why can it not find the class?
I believe I have it fixed. I am used IntelliJ and under Project Structure -> Artificats -> Output Layout.
My Jars I was adding with Maven were getting added to the "Available Elements" area. I had to move them into the Web-INF/lib folder.

Getting a RootDoc in jdk11

I am trying to test out some code that works with Java Doc, it is used under the maven-javadoc-plugin. I am trying to get it to work under jdk11. I am after an implementation of RootDoc which I can use when running tests.
Currently the tests use EasyDoclet which gives me a RootDoc like so:
EasyDoclet easyDoclet = new EasyDoclet(new File("dir"), "com.foo.bar");
RootDoc rootDoc = easyDoclet.getRootDoc()
However I could not get this to work under jdk11.
The first issue I had was tools.jar is missing so I changed my pom.xml to have:
<dependency>
<groupId>org.seamless</groupId>
<artifactId>seamless-javadoc</artifactId>
<version>1.1.1</version>
<exclusions>
<exclusion>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- maybe this will get what ever was in tools.jar -->
<dependency>
<groupId>com.github.olivergondza</groupId>
<artifactId>maven-jdk-tools-wrapper</artifactId>
<version>0.1</version>
</dependency>
This lead to many instances of:
java.lang.NoClassDefFoundError: com/sun/tools/javadoc/PublicMessager
The PublicMessager class seems to exist to make public some constructors, I am not sure why it exists under the com.sun.tools package. I tried to make a copy of this class:
public static class PublicMessager extends
com.sun.tools.javadoc.main.Messager {
public PublicMessager(Context context, String s) {
super(context, s);
}
public PublicMessager(Context context, String s, PrintWriter printWriter, PrintWriter printWriter1, PrintWriter printWriter2) {
super(context, s, printWriter, printWriter1, printWriter2);
}
}
And the error message changes to:
java.lang.IllegalAccessError: superclass access check failed: class com.fun.javadoc.FooBar$PublicMessager (in unnamed module #0x4abdb505) cannot access class com.sun.tools.javadoc.main.Messager (in module jdk.javadoc) because module jdk.javadoc does not export com.sun.tools.javadoc.main to unnamed module #0x4abdb50
I exposed jdk.javadoc to the unnamed module using:
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-Dfile.encoding=UTF-8</argLine>
<argLine>--add-opens=jdk.javadoc/com.sun.tools.javadoc.main=ALL-UNNAMED</argLine>
</configuration>
</plugin>
</plugins>
</build>
This meant that my custom version of PublicMessager would no longer have the errors shown however the version from seamless under com.sun.tools could not be found. I made my own version of EasyDoclet which used my PublicMessager however it turned out that the following two classes are missing:
import com.sun.tools.javadoc.JavadocTool;
import com.sun.tools.javadoc.ModifierFilter;
At this point I am not sure what to do. halp!
Perhaps an alternative would be to instead find the jdk11 equivalent of RootDoc which I think is DocletEnvironment and then some how get an implementation of that, I have no idea how to get an implementation of DocletEnvironment.

EnableElasticSearchRepositories exception

I am using spring-data-elasticsearch version using 1.3.0.BUILD-SNAPSHOT
The following code was working fine until a few weeks ago. Suddenly it started giving me an exception. I have tried giving basePackages instead of value..
/* We will require this at the point of deployment */
#EnableElasticsearchRepositories(basePackages = "com/rentomoney/rom/server/data/search/repository")
#Configuration
public class ROMElasticSearchConfig {
....
}
OR
#EnableElasticsearchRepositories( "com.rentomoney.rom.server.data.search.repository")
#Configuration
public class ROMElasticSearchConfig {
....
}
Here is the exception that is being generated:
nested exception is java.lang.annotation.AnnotationFormatError:
Invalid default: public abstract java.lang.Class
org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories.repositoryBaseClass()
at
org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:70)
at
org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:85)
at
org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:76)
at
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.importBeanDefinitionResource(DefaultBeanDefinitionDocumentReader.java:261)
at
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseDefaultElement(DefaultBeanDefinitionDocumentReader.java:186)
at
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:171)
at
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:144)
at
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:100)
at
org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:510)
at
org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:392)
The error is a bit cryptic, but it really means that it cannot find the default DefaultRepositoryBaseClass class due to a missing dependency.
Signature for repositoryBaseClass in EnableElasticsearchRepositories annotation:
Class<?> repositoryBaseClass() default DefaultRepositoryBaseClass.class;
If you are using SNAPSHOT versions, you'll need to have the SNAPSHOT repository configured along with any required SNAPSHOT dependencies that the jar doesn't include. Make sure you have the following in your POM file:
Repositories:
<repository>
<id>spring-snapshot</id>
<name>Spring Maven SNAPSHOT Repository</name>
<url>http://repo.springsource.org/libs-snapshot</url>
</repository>
Dependencies:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>1.11.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>1.3.0.BUILD-SNAPSHOT</version>
</dependency>
That solved the issue for me.
First of all its not suggested to use snapshot version unless you are using it for new feature testing. (i.e 1.3.0.BUILD-SNAPSHOT in your case)
The current stable and released version for spring data elasticsearch is
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>1.2.0.RELEASE</version>
</dependency>
Second about your question getting error for #EnableElasticsearchRepositories, it could be related to this (DATAES-136) recent commit to the project.
Share some more information / code if problem persist

ClassNotFound exception using Jackson ObjectMapper

I have a Spring 3 MVC app that I am setting up some ajax actions for.
My controller action looks like this:
#RequestMapping(value="add", method=RequestMethod.POST)
#Secured("ROLE_USER")
#ResponseStatus(HttpStatus.CREATED)
public #ResponseBody Plan addPlan(#RequestBody Plan plan, Principal principal) {
//Save the plan
}
When I post the Plan data from my browser the app throws a ClassNotFound exception:
java.lang.ClassNotFoundException: org.joda.time.ReadableInstant not found by jackson-mapper-asl [176]
at org.apache.felix.framework.ModuleImpl.findClassOrResourceByDelegation(ModuleImpl.java:787)
at org.apache.felix.framework.ModuleImpl.access$400(ModuleImpl.java:71)
at org.apache.felix.framework.ModuleImpl$ModuleClassLoader.loadClass(ModuleImpl.java:1768)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
The Plan object itself does not contain any joda-date types. Though it contains a collection of objects that do. Originally I was pulling in the joda-date jar via my DOA jar but the error persists even if I add a direct dependency to my web project's pom.xml. I'm using the joda classes elsewhere in this project without any issue.
Additional information
Here are the relevant dependencies from my web pom.xml:
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.3</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.3</version>
</dependency>
I somehow came across this question: Apache FTP server is not seeing a logging jar package that exists in the class path
Their solution of setting <class-loader delegate="false"> in glassfish-web.xml seems to have fixed my issues.
I've reported this on Glassfish JIRA https://java.net/jira/browse/GLASSFISH-20808

Categories