https://bz.apache.org/bugzilla/show_bug.cgi?id=69262

            Bug ID: 69262
           Summary: Application can fail to start if both
                    jakarta.el::jakarta.el-api and
                    org.apache.tomcat.embed::tomcat-embed-el are on the
                    classpath
           Product: Tomcat 10
           Version: 10.1.26
          Hardware: All
                OS: All
            Status: NEW
          Severity: normal
          Priority: P2
         Component: EL
          Assignee: dev@tomcat.apache.org
          Reporter: skylar.sut...@gmail.com
  Target Milestone: ------

If both the jakarta.el::jakarta.el-api and
org.apache.tomcat.embed::tomcat-embed-el JARs are on the classpath, the
application can fail to start.

Both JARs contain a jakarta.el.ExpressionFactory, and if the jakarta.el-api
version gets prioritized higher on the classpath, it will trump the
tomcat-embed-el version. This may manifest differently for each application,
but in our case it manifested as a "java.lang.ClassNotFoundException:
com.sun.el.ExpressionFactoryImpl" at startup. 

ref:
https://github.com/jakartaee/expression-language/blob/master/api/src/main/java/jakarta/el/ExpressionFactory.java
ref:
https://github.com/apache/tomcat/blob/main/java/jakarta/el/ExpressionFactory.java

Given that jakarta.el-api is the API spec and tomcat-embed-el is the
implementation, it should be assumed that a) both may be on the classpath and
b) that it is safe for both to be on the classpath (e.g. foo-api and foo-impl).

In reviewing the jakarta.el-api version of the ExpressionFactory, their
documentation outlines a clear workflow of how to tell the Factory what your
implementation class is. I belive the tomcat-embed-el ExpressionFactory should
NOT be an override of the API class, but rather something like
"ExpressionFactoryImpl" which instructions to ExpressionFactory that it should
use ExpressionFactoryImpl.

An example POM below shows a valid use case that triggered this scenario: a
springboot application, running on Tomcat Embed (Jasper), using the JSTL tag
reference implementation. The springboot WAR is generated with jakarta.el-api
prioritized higher than tomcat-embed-el in the "classpath.idx" file, which
determines the classpath order for a springboot application. Adding an
<exclusion /> for "el-api" to "jstl-api" will resolve the issue and allow the
application to start. 

e.g. 
<dependency>
        <!-- Jakarta JSTL (Standard Tag Library) API -->
        <groupId>jakarta.servlet.jsp.jstl</groupId>
        <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
        <scope>runtime</scope>
        <exclusions>
                <exclusion>
                        <!-- Tomcat Jasper includes a jakarta.el.** package
which overrides "jakarta.el-api" -->
                        <groupId>jakarta.el</groupId>
                        <artifactId>jakarta.el-api</artifactId>
                </exclusion>
        </exclusions>
</dependency>


Broken Example:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0";
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd";>
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.foo</groupId>
        <artifactId>bar</artifactId>
        <version>1.0.0-SNAPSHOT</version>

        <name>foo-bar</name>
        <packaging>war</packaging>

        <parent>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>3.3.2</version>
        </parent>

        <properties>
                <maven.version>3.2.5</maven.version>
        </properties>

        <dependencies>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot</artifactId>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-web</artifactId>
                        <scope>runtime</scope>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-autoconfigure</artifactId>
                </dependency>
                <dependency>
                        <groupId>org.apache.tomcat.embed</groupId>
                        <artifactId>tomcat-embed-core</artifactId>
                </dependency>
                <dependency>
                        <groupId>org.apache.tomcat.embed</groupId>
                        <artifactId>tomcat-embed-jasper</artifactId>
                </dependency>
                <dependency>
                        <groupId>jakarta.servlet.jsp.jstl</groupId>
                        <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
                        <scope>runtime</scope>
                        <exclusions>
                                <exclusion>
                                        <groupId>jakarta.el</groupId>
                                        <artifactId>jakarta.el-api</artifactId>
                                </exclusion>
                        </exclusions>
                </dependency>
                <dependency>
                        <!-- Currently, Tomcat 10 does not ship with a JSTL
implementation out of the box, use the Glassfish
                                reference impl:
https://stackoverflow.com/a/4928309/215166 -->
                        <groupId>org.glassfish.web</groupId>
                        <artifactId>jakarta.servlet.jsp.jstl</artifactId>
                        <scope>runtime</scope>
                </dependency>
        </dependencies>

        <build>
                <plugins>
                        <plugin>
                                <!--
                                        The Spring Boot Maven Plugin packages
the final artifact as a runnable springboot WAR and adds the
                                        correct MANIFEST.MF entries to allow
simple startup like: java -jar artifact.war
                                        ref:
https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/htmlsingle/
                                -->
                                <groupId>org.springframework.boot</groupId>
                               
<artifactId>spring-boot-maven-plugin</artifactId>
                                <configuration>
                                       
<mainClass>com.foo.bar.Application</mainClass>
                                        <layout>WAR</layout>
                                </configuration>
                        </plugin>
                </plugins>
        </build>
</project>

-- 
You are receiving this mail because:
You are the assignee for the bug.
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to