We are the Dev Teams of
  • brands
  • ebay_main
  • ebay
  • mobile
<
>
BLOG

Moving from war to jar with Spring

by Peter Laufer
in Tutorials

I recently stumbled over a small internal webapp that we use in order to browse, search and filter feedback from our users. I wanted to implement a tiny tweak that would extend the search feature over a few more fields in the data. Looked easy and was in fact quickly done. Only local testing proved to be difficult as the app required a web server (in this case Tomcat) and wasn't that easy to set up. Given the scope of the change and the fact that the app was only used internally I took the bold shortcut and deployed to production stage immediately. No issues, my feature worked - everybody was happy.

The change of a webapp that hadn't been touched since a while seemed to ring a warning bell at my colleagues from Site Operations - ouch. One of them approached me and asked whether I could pimp the app again this time to produce a standalone jar instead of a war file so that they could easily deploy it to our platform as a service (PaaS) infrastructure based on Docker. 

It's always good be friends with Site Ops so I thought I should give it a try.

The App

Here’s what the app looked like before the migration:

  • Spring 2.5.6 with XML application context
  • Persistence with Hibernate ORM and a MySQL database
  • Template rendering with Freemarker
  • About a dozen controllers
  • Maven build packaging a war file
  • Tomcat 7 (provided in production) configured via web.xml

It’s not exactly a big project and as it was based on Spring MVC already I thought that a migration to Spring Boot could be the easiest way to upgrade the stack a little and end up with a standalone jar.

Side note:
There are a couple of hints out there on converting an existing application to Spring Boot, but as it’s mostly about certain bits and pieces of the process I figured that writing down my experience could help others that try to achieve the same.

The Migration

To base the app on Spring Boot I had to make some changes to the projects pom.xml first:

  • Switch packaging from war to jar
  • Use spring-boot-starter-parent as a parent POM
  • Add a dependency to spring-boot-starter-web (this pulls in the basic Spring CI and Web MVC jazz as well as an embedded Tomcat)
  • To keep using Freemarker for templating add a dependency to spring-boot-starter-freemarker
  • Add an explicit spring-orm dependency (Hibernate and ORM aren’t part of the default bundle)
  • Remove dependencies to spring, spring-webmvc and servlet-api (spring-boot-starter-web includes all of that already)
  • Remove the maven-war-plugin configuration
  • Add the spring-boot-maven-plugin instead

Despite all these changes I was still able to successfully compile the project afterwards. (Thanks to the Spring guys that managed to keep their core APIs and classes pretty stable over the releases between 2.5.6 and 4.2.5!)

Of course compilation alone doesn’t help much. For a standalone jar to run we need a main class. I started with a very simple one just trying to import the good old XML application context:

@SpringBootApplication
@ImportResource("application-context.xml")
public class Server {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Server.class, args);
    }

}

To make the jar executable and allow an easy app start via mvn spring-boot:run I added a start-class property to the properties section of the pom.xml pointing to my new Server main class.

I got something running this way, but it wasn’t the app like I used to know it. Obviously, the Tomcat setup – servlets etc. – was still defined in the web.xml that Spring Boot doesn’t care about. The SimpleUrlHandlerMapping from the XML application context didn’t seem to work and I didn’t get a template to render.

A lot of files simply weren’t in the right place where Spring Boot would pick them up as well. The classic WEB-INF folder isn’t the natural place where Spring Boot is looking for resources. Here a list of files and folders that I had to move:

  • Freemarker templates: from src/main/webapp/WEB-INF/ftl to src/main/resources/templates
  • static assets: from src/main/webapp/[css|images|js] to src/main/resources/static/[css|images|js]

As the URL mapping still wasn’t working and I don’t like XML application contexts anyway, I decided to switch all controllers to annotation-based configuration by adding @Controller and @RequestMapping annotations (and while I was at it switch the classic setter dependency injection to @Inject constructor dependency injection). I removed all the corresponding bean declarations from the XML context.

The application also used a UrlFilenameViewController that would map certain resources from “/path/page.html” directly to the corresponding template under “templates/path/page.ftl”. This thing refused to work and thus I replaced it with a WebMvcConfigurerAdapter:

@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/index.html").setViewName("index");
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/slideshow.html").setViewName("slideshow");
        registry.addViewController("/test.html").setViewName("test");
    }

} 

After these changes there wasn’t much left in the XML application context (basically only the datasource and Freemarker configurer). I went all in for Java config and could finally delete the web.xml and the application-context.xml files.

The app could be started now from my IDE just by running the main class and was serving static content and my rendered templates just fine.

I packaged the jar and deployed it to our PaaS infrastructure only to realize that it wouldn’t start up there, because of a FileNotFoundException. I had hit the last gotcha on my road. One of the classes was trying to load config data from the classpath via resource.getFile() which worked fine in the web server because it could load the file from the WEB-INF folder, but didn’t work with resources loaded from the jar file. Fortunately, this could be fixed by switching the code to resource.getInputStream() and the app would finally run smoothly from the standalone jar. :)

Conclusion

I’d say it was fairly easy to migrate from a classic Spring MVC app packaged into a war and deployed to a Tomcat web server to a standalone Spring Boot jar. As a side effect the app is now way easier to run and test on a local machine, got upgraded to the most recent Spring dependencies and Java 8 along the way and is a joy to deploy and maintain for my colleagues from Site Operations.

If this experience report proves helpful to you let me know. It would make this little experiment even more worthwhile for me.

spring, refactoring, legacy code, maven, java

?>