Issue
I have implemented a REST service using Jersey and wanted to use Bean Validation. When I add the bean validation maven dependency for Jersey (jersey-bean-validation), the webapp breaks on startup due to an error:
HV000183: Unable to initialize 'jakarta.el.ExpressionFactory'. Check that you have the EL dependencies on the classpath, or use ParameterMessageInterpolator instead
I searched around and found older solutions mentioning including missing dependencies, such as this question:
javax.validation.ValidationException: HV000183: Unable to load 'javax.el.ExpressionFactory'
but the dependencies are outdated in my case. Also, when looking at the dependency tree:
[INFO] org.example:valtest:war:1.0-SNAPSHOT
[INFO] +- org.glassfish.jersey.containers:jersey-container-servlet:jar:3.0.2:compile
[INFO] | +- org.glassfish.jersey.containers:jersey-container-servlet-core:jar:3.0.2:compile
[INFO] | +- org.glassfish.jersey.core:jersey-common:jar:3.0.2:compile
[INFO] | | +- jakarta.annotation:jakarta.annotation-api:jar:2.0.0:compile
[INFO] | | \- org.glassfish.hk2:osgi-resource-locator:jar:1.0.3:compile
[INFO] | +- org.glassfish.jersey.core:jersey-server:jar:3.0.2:compile
[INFO] | | \- org.glassfish.jersey.core:jersey-client:jar:3.0.2:compile
[INFO] | \- jakarta.ws.rs:jakarta.ws.rs-api:jar:3.0.0:compile
[INFO] +- org.glassfish.jersey.inject:jersey-hk2:jar:3.0.2:compile
[INFO] | +- org.glassfish.hk2:hk2-locator:jar:3.0.1:compile
[INFO] | | +- org.glassfish.hk2.external:aopalliance-repackaged:jar:3.0.1:compile
[INFO] | | +- org.glassfish.hk2:hk2-api:jar:3.0.1:compile
[INFO] | | \- org.glassfish.hk2:hk2-utils:jar:3.0.1:compile
[INFO] | \- org.javassist:javassist:jar:3.25.0-GA:compile
[INFO] \- org.glassfish.jersey.ext:jersey-bean-validation:jar:3.0.2:compile
[INFO] +- jakarta.inject:jakarta.inject-api:jar:2.0.0:compile
[INFO] +- jakarta.validation:jakarta.validation-api:jar:3.0.0:compile
[INFO] +- org.hibernate.validator:hibernate-validator:jar:7.0.0.Final:compile
[INFO] | +- org.jboss.logging:jboss-logging:jar:3.4.1.Final:compile
[INFO] | \- com.fasterxml:classmate:jar:1.5.1:compile
[INFO] +- jakarta.el:jakarta.el-api:jar:4.0.0:compile
[INFO] \- org.glassfish:jakarta.el:jar:4.0.0:compile
I can see at the very bottom that the el-api and el libraries are included as transitive dependencies of the jersey-bean-validation library already.
I put a breakpoint into ResourceBundleMessageInterpolator to find out the cause of this, and when the ExpressionFactory is getting instanciated, an exception is thrown:
java.util.ServiceConfigurationError: jakarta.el.ExpressionFactory: org.apache.el.ExpressionFactoryImpl not a subtype
I don't know where that comes from. I can at least create an ExpressionFactory as expected using:
ExpressionFactory.newInstance()
I have created a very simple example project that uses the same Jersey+Jetty setup with maven that I use in my project: https://www.dropbox.com/s/dcvief9dlqk648m/valtest.zip?dl=0
It can be run with mvn jetty:run
and it should display a message on http://localhost:8080/test.
What am I missing?
Solution
tl;dr;
The Jetty Maven plugin pulls in a conflicting dependency. The dependency deals with JSP. If you don't have any need for JSP in your application, I think the following solution should be safe. You just need to exclude the bad dependency
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>11.0.2</version>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>apache-jsp</artifactId>
<version>11.0.2</version>
<exclusions>
<exclusion>
<groupId>org.mortbay.jasper</groupId>
<artifactId>apache-el</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</plugin>
So how the ExpressionFactory
is found, is through a ServiceLoader
. The org.glassfish:jakarta.el
jar doesn't have the required META-INF/services
file to be found through the ServiceLoader
. But after the ServiceLoader
fails, other options will be tried. So ultimately, the jar's ExpressionFactoryImpl
will be found. But the problem is that the Jetty Maven plugin also pulls in an implementation, which does have the META-INF/services
file. So that is the implementation that will be used.
After some debugging, and stepping into the ServiceLoader code, I came across this error when the class was found:
"jakarta.el.ExpressionFactory: org.apache.el.ExpressionFactoryImpl not a subtype"
So I created a new Maven project and added the jetty-maven-plugin as a dependency. After searching through all the jars, I found the o.a.e.ExpressionFactoryImpl
class. It was in the apache-el
jar that Jetty pulled in. The weird part is that the class actually does implement the jakarta.el.ExpressionFactory
. I thought that maybe the problem was that it implemented the old javax
class, but that wasn't the case. I'm not completely sure why this causes an error. But after getting that out of the way, it began to work.
Another option if removing apache-el
is not desired
One thing I tried before I got the above solution was to try to manually add the META-INF/services
file to the the org.glassfish:jakarta.el
jar. What I did was the following
cd ~/.m2/repository/org/glassfish/jakarta.el/4.0.0
mkdir -p META-INF/services
echo com.sun.el.ExpressionFactoryImpl > META-INF/services/jakarta.el.ExpressionFactory
jar uf jakarta.el-4.0.0.jar META-INF/services/jakarta.el.ExpressionFactory
# You may want to make a copy of the original dependency before you do this
# so you can easily revert back to the original if needed.
This may not be a desirable solution either, but it worked.
If you need the apache-el
, you can also try to replace it with the org.glassfish:jakarta.el
and see if that works. I haven't tried it yet, but I'm not sure if Jetty will run into similar problems with a different implementation or not.
Answered By - Paul Samsotha