JSPs compile to Java code at run time. This is helpful if we want to test code changes without a build and deploy. However, if errors are introduced, they may not be spotted till it’s too late. A useful compromise is to validate JSPs at build time to verify that they will compile. The validator catches syntax errors before the application deploys and starts. This speeds up our build and test cycle and prevents silly mistakes slipping through to production code.
We can pre-compile JSPs in a Maven build to validate JSPs at build time. Any errors that prevent compilation will halt the build and fail faster.
JSP webapp lifecycle
Here’s a recap of the problem. Regular Java code is written in .java files and then compiled to .class files. On this compilation step, the compiler helps us by checking that the Java code is valid. If the Java code does not compile, the compiler halts and informs us of the error. As a result, it is not possible to deploy invalid Java code to an application server.
JSPs on the other hand are not compiled at build time. Instead, they are simply packaged into a web app (usually a .war file) in plain text. It’s only when the end user requests the page that the application server translates the JSP file to Java code and then compiles it to a .class file. If there is an error in the JSP code causing this translation and compilation to fail, then the user sees an error message. Just to make things worse, the default error page is pretty nasty.
So, it is possible for the developer to introduce an error into a JSP and for no-one to notice until they build it, deploy it and then wait for an end user to access the problem page.
A limited solution
The solution presented here is to shift the JSP compilation step left to build time. This will alert the developer immediately if there’s an error in a JSP. It will also give the team more confidence in automated CI builds – if the application builds then we know there are no JSP syntax errors.
Just to be clear, this solution catches only errors in Java code embedded in JSP scriptlets. It will not catch invalid HTML or JSP tags and it certainly won’t catch plain old logic bugs. Also, I’ll point out that JSPs really shouldn’t contain more than a few lines of trivial Java code, if any. If you find JSPs containing lengthy scriptlet sections, consider moving the Java code out to a Java file where it will be happier. However, this solution is handy for increasing confidence in poorly factored legacy code.
Validate JSPs with jetty-jspc-maven-plugin
The webapp here is a Maven project with the packaging. When we run mvn clean install, Maven compiles all the .java files on the compile phase. It then packages the resulting .class files along with the webapp folder (containing the JSPs) into a war file. We can compile the JSPs on the compile phase too using the jetty-jspc-maven-plugin:
<plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-jspc-maven-plugin</artifactId> <version>9.4.7.v20170914</version> <executions> <execution> <id>jspc</id> <goals> <goal>jspc</goal> </goals> <configuration> <mergeFragment>false</mergeFragment> <generatedClasses>${project.build.directory}/jspc</generatedClasses> </configuration> </execution> </executions> </plugin>
This configuration invokes the jspc goal. It’s bound to the compile phase by default. This means that it’s run immediately after the .java files are compiled.
Note a couple of configuration options here. In both cases this is to override the default plugin behaviour which is to add the compiled JSPs into the webapp. We just want to verify that the JSPs can compile. We’re don’t want to actually package the resulting compiled code into our application.
The result
If all of the JSPs compile, there is no effect on the build at all. The resulting webapp war is identical.
On the other hand, if any JSP fails to compile, the build will fail with an error message like this:
[ERROR] Failed to execute goal org.eclipse.jetty:jetty-jspc-maven-plugin:9.4.7.v20170914:jspc (jspc) on project spanners-mvc: Failure processing jsps: org.apache.jasper.JasperException: Unable to compile class for JSP: [ERROR] [ERROR] An error occurred at line: 2 in the jsp file: /WEB-INF/tiles/footer.jsp [ERROR] Syntax error, insert ";" to complete LocalVariableDeclarationStatement [ERROR] 1: <% [ERROR] 2: String footerText = "Copyright 2017" [ERROR] 3: %> [ERROR] 4: <p><%=footerText%></p>
In this case, I’ve just missed a semicolon from the end of a line. The error message is clear and the error is reported when we need it – before the code leaves our machines!
Source code showing how to validate JSPs in this way is in the spanners-mvc project of spanners 3.6, available on GitHub.
Thanks for your post.
I have to ask. Why not leverage this pre-compilation as an advantage to our end users? If the JSP page is compiled at build time (not at runtime), the first soul who requests the page won’t have to wait 2 to 3s for a response.
Is it possible?
Marcio
Yes, absolutely! Indeed, this is the main purpose of the jspc plugin. See the current docs at https://www.eclipse.org/jetty/documentation/9.4.28.v20200408/jetty-jspc-maven-plugin.html (dead link fixed in article).
In my use case I want Tomcat to compile JSPs on the fly. So I use the jspc plugin to compile the JSPS and then throw away the resulting classes by setting generatedClasses to a directory that isn’t included in the war.
To include the precompiled classes in the war as you suggest, just use the default plugin configuration.
When gitlab does the compile phase I get errors such as the following:
[WARNING] Compilation error
java.util.concurrent.ExecutionException: java.lang.NoClassDefFoundError: javax/servlet/http/HttpSessionBindingListener
the pom.xml includes standard dependencies for javax.servlet-api and jax-servlet.jsp
What I am i missing? the project compiles and builds war just fine when i remove the jetty-jspc-maven-plugin plugin
Thanks!
Hi Steve!
In my example HttpSessionBindingListener comes from geronimo-servlet_3.0_spec dependency. Suggest you try swapping out your servlet-api for geronimo-servlet_3.0_spec to see if that helps.
Stuart,
Thanks for the tip! Do you know of a way to also validate JSTL so that invalid properties are caught and cause the build to fail? All it takes is a simple typo referencing a property of an object and the build will go fine and we won’t know until a user hits that JSP view.
Hi Steve
I don’t think I’ve ever tried that and it’s been a while since I’ve worked with JSTL. My understanding is that JSTL tags are compiled down along with the JSP into the .class file. If they translate to invalid Java code and you’re compiling the JSPs at build time, I’d expect invalid object references to be caught. I’m not sure about that though. If I were working with JSTL I’d start by creating some JSTL wth invalid properties / object references and see what happens when it’s compiled.