When I must pre-compile JSP with tomcat, it’s usually pain because it takes a lot of time. Let’s see How compile this faster.

How to compile JSP

If I must compile for tomcat, I usually take this Maven plugin : https://github.com/leonardehrenfried/jspc-maven-plugin I take this plugin because I think it’s pretty simple, and it give availabilty to add includes/excludes of jsp. This is the Maven config to do the job in my War project (we see that I have a tomcat 8 project) :

<plugin>
   <groupId>de.mytoys.maven.plugins</groupId>
   <artifactId>jspc-maven-plugin</artifactId>
   <version>1.1.0</version>
   <configuration>
      <webAppSourceDirectory>${project.build.directory}/${project.artifactId}-${project.version}</webAppSourceDirectory>
      <webXml>${project.build.directory}/${project.artifactId}-${project.version}/WEB-INF/web.xml</webXml>
      <generatedClasses>${project.build.directory}/jspc</generatedClasses>
   </configuration>
   <dependencies>
      <dependency>
         <groupId>org.apache.tomcat</groupId>
         <artifactId>tomcat-jasper</artifactId>
         <version>8.5.4</version>
         <exclusions>
            <exclusion>
               <groupId>org.eclipse.jdt.core.compiler</groupId>
               <artifactId>ecj</artifactId>
            </exclusion>
         </exclusions>
      </dependency>
      <dependency>
         <groupId>org.eclipse.jdt.core.compiler</groupId>
         <artifactId>ecj</artifactId>
         <version>4.6.1</version>
      </dependency>
   </dependencies>
</plugin>

Some problems of this plugin

If we have many jsp (> 500) the plugin can take lot of time to compile all files. (see the benchmark after) The other problem is this plugin will stop and fail at the first jsp who contains error. And even at the first error in the jsp! If a jsp contains 5 errors, we must run 5 times the plugin to discover all. So if a big project contains multiples errors, we must run may times the plugin and so many times the full compilation of all jsp.

First fix : performance

In order to compile a big project faster, I propose different things

Give all jsp to Jasper JSPC

In version 1.1.0 of the plugin, it takes all of the jsp and call execute JSPC for each of them. This is the code :

for (String fileName : jspFiles) {
	jspc.setJspFiles(fileName);
	getLog().info("Compiling " + fileName);
	jspc.execute();
}

Maybe it’s an optimization for Tomcat 6.X, but I think with Tomcat 8.X it’s slower than giving all JSP directly to JSPC.execute(); See pull request https://github.com/leonardehrenfried/jspc-maven-plugin/pull/1 to fix this.

Execute compilation in parallel

To compile faster we need to compile in parallel. Indeed when we launch JavaMissionControl for example, we see the CPU is 20%-30% usage when compiling. To increase CPU usage, we need to execute jspc in different thread. See pull request https://github.com/leonardehrenfried/jspc-maven-plugin/pull/2 to add this feature.

Benchmark

I made a little benchmark with two projects : one with 400 jsp and one with 2700 jsp. I test this on HP ZBook i7 Windows 7.

  • 400 jsp

Time Java Heap CPU

out of the box 1.1.0

50s

1.3Gb

20%-30%

Giving all jsp to jspc.execute

30sec

1.1Gb

20%-30%

With 2 threads

21s

1.35Gb

60%

With 4 threads

17s

1.35Gb

90%

  • 2700 jsp

Time Java Heap CPU

out of the box 1.1.0

18min18s

1.35Gb

20%

Giving all jsp to jspc.execute

3min43s

1.35Gb

20%

With 2 threads

2min5s

1.35Gb

40%-60%

With 4 threads

1min25

1.35Gb

80%

Second fix : stop at first error

To avoid this behavior, I add new parameter : stopAtFirstError (true by default for retro-compatibility). Indeed Jasper JSPC provide a parameter "failOnError" wich allow to continue even if error were raised. This feature works only if we gave all the jsp to jspc. (see first fix). But we need to fail maven build if we have jsp error, but when we call jspc with "failOnError=false", it raise no exception. So the only way I found right now is to add a Log handler who tell the Mojo if jspc raise a SEVERE error in his log (this is a little be tricky…​). If we take multiple threads, this parameter works only if it’s "false". If it’s true, each thread with stop at first error. See pull request https://github.com/leonardehrenfried/jspc-maven-plugin/pull/2 to add this feature.

Comparing to other Maven plugins

During this case I search other Maven plugins who compile JSP with Tomcat. It’s not easy because JSP it’s very fashionable…​ I found this major ones :

  • https://github.com/leonardehrenfried/jspc-maven-plugin

    • no recent upgrade

    • provide includes/excludes parameters

    • trace all jsp in log when compiling

    • fork of jetty-jspc-maven-plugin

  • https://github.com/Jasig/jspc-maven-plugin

    • 1 recent upgrade

    • allow different version of tomcat compiler (by design)

    • fork of the Codehaus jspc-maven-plugin

    • does not provide stopAtFirstError=false for now (but there are current PR for that)

    • does not provide includes/excludes parameters

    • trace all jsp in log when compiling

  • jspc-maven-plugin from codehaus

    • no more maintained

    • does not provide stopAtFirstError=false

    • does not provide tomcat 8 compiler (only tomcat 6)

  • https://www.eclipse.org/jetty/documentation/current/jetty-jspc-maven-plugin.html

    • design by and for jetty

    • if we give jasper dependency we can use this for tomcat

    • does not provide stopAtFirstError=false for now

    • trace all jsp in log at startup

And this is a little benchmark of this plugins with my project (2700 jsp).

Time Java Heap CPU

leonardehrenfried/jspc-maven-plugin 1.1.0

18min18s

1.35Gb

20%

leonardehrenfried/jspc-maven-plugin with my PR (4 threads)

1min25

1.35Gb

80%

Jasig/jspc-maven-plugin

3min22s

1.35Gb

20%

jspc-maven-plugin from codehaus

not possible because not support tomcat 8

jetty-jspc-maven-plugin

3min50s

1.37Gb

20%

Conclusion

If the wholes PR are accepted, the plugin will be much faster and will permit to not stop at the first error. I think this maybe start a "version 2.X" of this plugin because one PR need java 7. I think this plugin can give Tomcat 8 by default now (especially if it start 2.X version). If you want to use this plugin with this fix and features see this : https://github.com/leonardehrenfried/jspc-maven-plugin/pull/2

Annex

Jasig JSPC maven plugin config

<plugin>
	<groupId>org.jasig.mojo.jspc</groupId>
	<artifactId>jspc-maven-plugin</artifactId>
	<version>2.0.2</version>
	<executions>
		<execution>
		<id>jspc</id>
		<phase>package</phase>
		<goals>
			<goal>compile</goal>
		</goals>
		<configuration>
        	<inputWebXml>${project.build.directory}/${project.artifactId}-${project.version}/WEB-INF/web.xml</inputWebXml>
            <defaultSourcesDirectory>${project.build.directory}/${project.artifactId}-${project.version}</defaultSourcesDirectory>
		</configuration>
		</execution>
	</executions>

	<dependencies>
		<dependency>
			<groupId>org.jasig.mojo.jspc</groupId>
			<artifactId>jspc-compiler-tomcat8</artifactId>
			<version>2.0.2</version>
		</dependency>
	</dependencies>
</plugin>

jetty JSPC maven plugin config

<plugin>
	<groupId>org.eclipse.jetty</groupId>
	<artifactId>jetty-jspc-maven-plugin</artifactId>
	<version>9.3.14.v20161028</version>
	<executions>
		<execution>
		<id>jspc</id>
		<phase>package</phase>
		<goals>
			<goal>jspc</goal>
		</goals>
			<configuration>
            	<webXml>${project.build.directory}/${project.artifactId}-${project.version}/WEB-INF/web.xml</webXml>
				<webAppSourceDirectory>${project.build.directory}/${project.artifactId}-${project.version}</webAppSourceDirectory>
			</configuration>
		</execution>
	</executions>
     <dependencies>
      <dependency>
         <groupId>org.apache.tomcat</groupId>
         <artifactId>tomcat-jasper</artifactId>
         <version>${tomcatVersion}</version>
         <exclusions>
            <exclusion>
               <groupId>org.eclipse.jdt.core.compiler</groupId>
               <artifactId>ecj</artifactId>
            </exclusion>
         </exclusions>
      </dependency>
      <dependency>
         <groupId>org.eclipse.jdt.core.compiler</groupId>
         <artifactId>ecj</artifactId>
         <version>${eclipse.jdt.core.compiler.ecj.version}</version>
      </dependency>
   </dependencies>
</plugin>
comments powered by Disqus