I have Spring Boot app running with embedded Tomcat. I'm running it in docker container. When I try to follow this page about spring boot in container and build layered image I recieve error mentioned below when I try to start the container. I know that the best way would be to exclude old version of servlet-api from my dependecies but it's impossible as this dependency stops working while I'm doing that. Unfortunately I cannot also get rid of this dependency. Is there a way to force Spring Boot to use specific implementation from classpath? I've tried Jetty and Undertow and docker successfully started but then lib that is using older version didn't work properly.
Other question is why it's working when I'm copy just jar and start it?
Dockerfile that I'm trying to build:
FROM adoptopenjdk:11-jre-hotspot
ARG DEPENDENCY=target/dep
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","com.test.App"]
Dockerfile that works:
FROM adoptopenjdk:11-jre-hotspot
COPY /target/application.jar /app/application.jar
COPY /target/lib /app/lib
ENTRYPOINT ["java", "-jar", "app/application.jar"]
The one that works requires additional plugins in pom.xml to make it possible:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>build-info</id>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<finalName>ttom-osm-converter</finalName>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.tomtom.mep.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
Dependecies from POM:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency> <!-- lib contains servlet-api-2.5 -->
<groupId>com.test.lib</groupId>
<artifactId>client</artifactId>
<version>${model.client.version}</version>
</dependency>
Error:
java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [NonLoginAuthenticator[StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[/path]
***************************
APPLICATION FAILED TO START
***************************
Description:
An attempt was made to call a method that does not exist. The attempt was made from the following location:
org.apache.catalina.authenticator.AuthenticatorBase.startInternal(AuthenticatorBase.java:1220)
The following method did not exist:
'java.lang.String javax.servlet.ServletContext.getVirtualServerName()'
The method's class, javax.servlet.ServletContext, is available from the following locations:
jar:file:/app/lib/servlet-api-2.5.jar!/javax/servlet/ServletContext.class
jar:file:/app/lib/javax.servlet-api-4.0.1.jar!/javax/servlet/ServletContext.class
jar:file:/app/lib/tomcat-embed-core-9.0.29.jar!/javax/servlet/ServletContext.class
It was loaded from the following location:
file:/app/lib/servlet-api-2.5.jar
Action:
Correct the classpath of your application so that it contains a single, compatible version of javax.servlet.ServletContext
From the error about 'java.lang.String javax.servlet.ServletContext.getVirtualServerName()', we can see that is was added in Servlet 3.1: you should exclude servlet-api:2.5.
Use the following command:
mvn dependency:tree -Dincludes='*:servlet-api'
This will list you all all module/dependencies including servlet-api
.
Then excludes the bad version from dependencies: the com.test.lib
client should not even includes it in the first place, meaning the dependency should be provided in it as well.
<dependency> <!-- lib contains servlet-api-2.5 -->
<groupId>com.test.lib</groupId>
<artifactId>client</artifactId>
<version>${model.client.version}</version>
<exclusions> <exclusion> <groupId>...</groupId> <artifactId>servlet-api</artifactId> </exclusion> </exclusions>
</dependency>
Do note that the servlet-api
groupId changed over the course of time: that's probably one reason why Maven does not select the "good" servlet-api
.
And I would advise you to use maven-enforcer-plugin to lock these bad dependencies:
<rules>
<bannedDependencies>
<excludes>
<exclude>*:servlet-api:2.5</exclude>
</excludes>
</bannedDependencies>
</rules>
<fail>true</fail>
See http://maven.apache.org/enforcer/enforcer-rules/bannedDependencies.html for more information.
Now then you mention that your lib (com.test.lib
) seems to not work without servlet-api 2.5, which means it use code that was probably removed between Servlet 2.5 and 3.1: your only course of action is to upgrade your lib as to not depends on said code: