spring-boot: Document that the jdk.util.jar.enableMultiRelease system property must be set to false when using JSPs with Boot 3.0 and java -jar

Spring Boot 3.0.1 + JDK 17 + packaing war Startup Error by java -jar demo-springboot-jsp.war

I create a simple spring boot project , and config to support very simple jsp, and packing to war.

then run java -jar demo-springboot-jsp.war

if config spring boot 2.7.6 will run and startup success.

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
        	<version>2.7.6</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

if config spring boot 3.0.1 will run and startup fail.

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
        	<version>3.0.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

Error Description first

ERROR.txt

$ java -jar demo-springboot-jsp.war
...
2022-12-26T13:43:01.062+08:00  INFO 21566 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.4]
2022-12-26T13:43:01.293+08:00 ERROR 21566 --- [           main] org.apache.catalina.core.ContainerBase   : A child container failed during start

java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[]]
	at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122) ~[na:na]
... 

Compare two version war

Spring boot 2.7.6 , built war , has org/springframework/boot/loader/data/RandomAccessDataFile$1.class

Spring boot 3.0.1, built war , does not have this file.

My Project

├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           └── demospringbootjsp
        │               ├── DemoSpringbootJspApplication.java
        │               └── HelloJSPController.java
        ├── resources
        │   └── application.properties
        └── webapp
            ├── index.jsp
            └── WEB-INF
                └── jsp
                    └── hello.jsp

pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<!--<version>3.0.1</version>-->
		<version>2.7.6</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>demo-springboot-jsp</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo-springboot-jsp</name>
	<description>Demo Spring Boot JSP</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
	        <finalName>${project.artifactId}</finalName>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

application.properties

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

DemoSpringbootJspApplication.java

package com.example.demospringbootjsp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
public class DemoSpringbootJspApplication  extends SpringBootServletInitializer {
	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
		return application.sources(DemoSpringbootJspApplication.class);
	}
	
	public static void main(String[] args) {
		SpringApplication.run(DemoSpringbootJspApplication.class, args);
	}

}

HelloJSPController.java

package com.example.demospringbootjsp;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import java.util.Map;

@Controller
public class HelloJSPController {

    @RequestMapping(value="/hellojsp", method = RequestMethod.GET)
    public ModelAndView hello(Map<String, Object> model) {
    
        System.out.println("HelloJSP Controller hello");
        
        model.put("message", "HelloJSP Controller hello");

        // /WEB-INF/jsp/hello.jsp
        return new ModelAndView("hello");
    }
}

hello.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello</title>
</head>
<%
// debug
System.out.println("webapp / WEB-INF / jsp / hello.jsp ");
%>
<body>
   <h1 class="color-red">SpringBoot hello.jsp!!  ${message} </h1>
</body>
</html>

Build

mvn clean package

Run

cd target
java -jar demo-springboot-jsp.war

Spring boot 3.0.1

Spring boot 3.0.1 / 3.0.0 demo-springboot-jsp.war , can run ok in standalone apache-tomcat-10.1.4 (some dependency need add scope provided) but can’t use java -jar demo-springboot-jsp.war run and startup fail.

Same code, only change to 2.7.6 It can use java -jar demo-springboot-jsp.war run and startup OK.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 10
  • Comments: 24 (12 by maintainers)

Commits related to this issue

Most upvoted comments

I am facing exactly the same issue. It seems to be caused by tomcat-embed-jasper (JSP support for Tomcat).

The application was working just fine in Spring Boot 2.7. Then, I upgraded it to Spring Boot 3 and I was able to execute it without errors both in my Eclipse environment and from the command line by typing mvn -o spring-boot:run.

Then, I was able to generate the war file using a standard “mvn clean install”. But when I run Spring Boot using “java -jar myFile.war” I got the following error message:

java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[/vault]]
...
Caused by: java.lang.IllegalStateException: zip file closed
        at java.base/java.util.zip.ZipFile.ensureOpen(ZipFile.java:831)
        at java.base/java.util.zip.ZipFile.getManifestName(ZipFile.java:1057)
        at java.base/java.util.zip.ZipFile$1.getManifestName(ZipFile.java:1100)
        at java.base/java.util.jar.JarFile.getManEntry(JarFile.java:937)
        at java.base/java.util.jar.JarFile.checkForSpecialAttributes(JarFile.java:1000)
        at java.base/java.util.jar.JarFile.isMultiRelease(JarFile.java:389)
        at org.apache.tomcat.util.scan.JarFileUrlJar.<init>(JarFileUrlJar.java:68)
        at org.apache.tomcat.util.scan.JarFactory.newInstance(JarFactory.java:41)
        at org.apache.tomcat.util.scan.StandardJarScanner.process(StandardJarScanner.java:393)
        at org.apache.tomcat.util.scan.StandardJarScanner.processURLs(StandardJarScanner.java:328)
        at org.apache.tomcat.util.scan.StandardJarScanner.doScanClassPath(StandardJarScanner.java:271)
        at org.apache.tomcat.util.scan.StandardJarScanner.scan(StandardJarScanner.java:234)
        at org.apache.jasper.servlet.TldScanner.scanJars(TldScanner.java:262)
        at org.apache.jasper.servlet.TldScanner.scan(TldScanner.java:104)
        at org.apache.jasper.servlet.JasperInitializer.onStartup(JasperInitializer.java:83)
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5153)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
        ... 43 common frames omitted

After many trials and errors, I found the problem comes from Jasper. I tried different combinations of versions (Tomcat, Jasper) but nothing was working effectively.

Some other people got the problem too, see for example: https://stackoverflow.com/questions/74913190/spring-boot-apps-jar-not-working-issue-with-tomcat-embed-jasper

This incident is important because otherwise it is impossible to make JSP work with Spring Boot 3.

That might have to do until we can get to the JarFile rewrite.

That’s a good idea. It fixes the sample above:

 java -Djdk.util.jar.enableMultiRelease=false  -jar target/demo-springboot-jsp.war

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.1)

2023-01-18T17:54:39.390Z  INFO 65312 --- [           main] c.e.d.DemoSpringbootJspApplication       : Starting DemoSpringbootJspApplication v0.0.1-SNAPSHOT using Java 17.0.5 with PID 65312 (/Users/awilkinson/dev/temp/gh-33633/target/demo-springboot-jsp.war started by awilkinson in /Users/awilkinson/dev/temp/gh-33633)
2023-01-18T17:54:39.393Z  INFO 65312 --- [           main] c.e.d.DemoSpringbootJspApplication       : No active profile set, falling back to 1 default profile: "default"
2023-01-18T17:54:40.138Z  INFO 65312 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-01-18T17:54:40.147Z  INFO 65312 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-01-18T17:54:40.147Z  INFO 65312 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.4]
2023-01-18T17:54:40.425Z  INFO 65312 --- [           main] org.apache.jasper.servlet.TldScanner     : At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
2023-01-18T17:54:40.506Z  INFO 65312 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-01-18T17:54:40.509Z  INFO 65312 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1074 ms
2023-01-18T17:54:40.795Z  INFO 65312 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-01-18T17:54:40.808Z  INFO 65312 --- [           main] c.e.d.DemoSpringbootJspApplication       : Started DemoSpringbootJspApplication in 1.749 seconds (process running for 2.081)

Perhaps we should just document it rather than disabling multi-release jars out of the box?

Thanks very much, @syphr42. The change is indeed in 10.1.6 and later. Closing in favour of https://github.com/spring-projects/spring-boot/issues/34582 where we upgraded to 10.1.7. I have also updated the migration guide to remove the section on JSPs as this should now work out of the box with Spring Boot 3.0.5 and later or with earlier versions and a manual Tomcat upgrade.

Thanks for the offer, @garretwilson. We’ve added a small section to the migration guide but I think it may also be worth adding a bullet to the list of JSP limitations in the reference documentation. A pull request that does so would certainly be welcome. The source for that section of the documentation is https://github.com/spring-projects/spring-boot/blob/3.0.x/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/servlet.adoc#jsp-limitations.

As a workaround you could unpack the jar and run it with something like:

java -classpath "app/WEB-INF/lib/*;app/WEB-INF/classes/" com.example.jsptest.Application