Saturday, May 8, 2010

Tips for less painful webapp development on Tomcat

Here are a few things I do to make my life easier when developing Java webapps deployed on Tomcat. These tips cover:

  • Rebuilding my application faster
  • Tomcat configuration options I like to use
  • Running Tomcat
  • Hot deploying of modified code



Building your application quickly

Mainstream IDEs like Eclipse, Netbeans and IntelliJ IDEA compile Java code pretty well. Unfortunately, a nontrivial Java webapp contains a lot of other stuff. The Ant build process that I have seen in all webapp projects for Tomcat go something like this:
  • Compile Java classes.
  • Build jars from these classes and copy then into build/myapp/WEB-INF/lib, or ...
  • Copy the classes into build/myapp/WEB-INF/classes folder.
  • Build non-Java components (such as Flash/Flex) and copy into build/myapp. 
  • Copy static files (HTML, JS, CSS, XML etc) into build/myapp. At this point, you have a fully deployable webapp image in build/myapp.
  • Build a war to copy into $CATALINA_HOME/webapps.
This can be a rather slow process, especially if you just want to change one line of Java code. Most IDEs will recompile Java code for you almost instantly, but an Ant-driven build leaves no place for your IDE to do its thing. What I do is to have Ant do an initial build, then have my IDE recompile the Java code when I make incremental changes to the Java code.

I take advantage of the fact that Ant builds a staging webapp deployment in build/myapp. For development purposes, there is no need to build a war or copy that stuff to $CATALINA_HOME/webapps. You can have Tomcat run your application in-place. If you have already deployed the application to the local Tomcat instance, you will find a configuration under conf, such as $CATALINA_HOME/conf/Catalina/localhost/myapp.xml. This file should contain a single Context element. Edit it so its docBase attribute points to where you built your application:

<context docbase="c:/path/to/my/build/myapp" path="/myapp"></context>


The next step is to configure your IDE's Java build output folder so that it compiles into your project's build/myapp/WEB-INF/classes folder. After that, every time your IDE compiles your Java code, you can just restart Tomcat to pick up your changes. It doesn't matter if you have jars in WEB-INF/lib with older versions of your classes, because the servlet spec requires that the webapp container load classes in WEB-INF/classes before WEB-INF/lib.

This should shorten your feedback loop. After building the project once, you can ignore Ant: let your IDE compile the Java class, restart Tomcat and try your changes.

Configuring Tomcat

The preferred way to set runtime options is to edit the $CATALINA_HOME/bin/setenv.bat (or setenv.sh) file. You could modify startup.bat/sh or catalina.bat/sh directly instead, of course. Using setenv.bat/sh is preferred because this file is not part of the Tomcat distribution, so it will not get overwritten each time you upgrade Tomcat. This is what I have in my setenv.bat:

set JAVA_OPTS=-Xmx512m -XX:MaxPermSize=256m -Xdebug -Xrunjdwp:transport=dt_socket,address=18999,server=y,suspend=n

The first two options increase heap and PermGen memory respectively, and the other options enable debugging by listening on port 18999 (you can substitute a different port number, of course).
  • If you working on a large code base or one that uses a lot of third party libraries, you might see an OutOfMemoryException for PermGen memory with the Sun JVM. Allocated separately from the heap, it's only 64MB by default, so it's worth increasing it. PermGen is the "permanent generation" part of the Sun JVM memory allocation: that's where the classes go.
  • While you can selectively enable debugging by running catalina.bat/sh with the "jpda" argument, that method does not specify the port address for the debugger to connect. For development purposes, I prefer to have debugging enabled at all times, with a specific port number that I can connect my debugger to. This way, I don't have to rely on my IDE's Tomcat debugging support: I can connect to and debug my application running on stock Tomcat at any time. All I need to do is tell my IDE (Eclipse) to connect to a remote Java application on port 18999.

Running Tomcat

Production systems typically configure Tomcat to run as a service so that it starts in the background automatically. In Windows, I prefer running the startup.bat file directly instead of running a Windows service when doing development. The reason is that you get the Java text window open: you can see the console output. Also, if you hit Ctrl-Scroll-Lock (used to be Ctrl-Break) to print the thread dump and memory usage summary. I configure my shortcut to startup.bat with a hotkey of Ctrl-Alt-F5. This means I can quickly shut down and restart Tomcat by closing the window and hitting Ctrl-Alt-F5.

Hot redeploy?

Tomcat restarts pretty fast. Still, wouldn't it be nice if you do not even need to restart Tomcat? In fact, Tomcat can detect and reload modified classes. Remember that Context element? If you add an attribute reloadable="true" to that element, Tomcat would theoretically reload your classes when you recompile them. Or you can use the manager application to reload. In practice, I have not had much luck with that approach. Tomcat is notoriously bad at reloading applications: the old classes often do not get freed, so each time you reload another copy of your classes get loaded into PermGen memory. This will rapidly exhaust your PermGen memory. Newer versions of Tomcat (both 6.x and 7) have received a lot of enhancements to prevent this sort of memory leak, or at least log enough information for you to fix such leaks. As of version 6.0.26, Tomcat still leaks PermGen memory badly for me right now, but it might be different for you.

Another limited way I could hot-deploy code changes was to step through the Java code in Eclipse's debugger. The Java JPDA debugging architecture provides HotSwap: a way for a debugger to replace a class in running code. When it works, it's great: as I step through the code, I can make a quick change and save, and Eclipse will recompile just that class and push the change to the running code. The program immediately uses the new code. In practice, this works only for minor code changes. Bigger changes, such as those that change a class signature, cannot be hot-swapped.

10 comments:

  1. "you can just restart Tomcat to pick up your changes"

    Man.... in 2004 I thought this would be fixed by 2010. I guess I was wrong.

    ReplyDelete
  2. More info on hotswap :)
    http://blog.redfin.com/devblog/2009/09/how_to_set_up_hot_code_replacement_with_tomcat_and_eclipse.html

    ReplyDelete
  3. If you develop with Eclipse you can also use the
    Sysdeo Tomcat Plugin
    http://www.eclipsetotale.com/tomcatPlugin.html

    It's quite old but still works in the newest Eclipse 3.5

    ReplyDelete
  4. Good post. I see people suffering, all the time, by the horrendous build process you described. Moreover it gets seriously worse when you swap Ant with Maven and Tomcat with a full scale app server.

    My solutions are very much like yours:

    1. Use "exploded war" structure for the web content. This means web folder stays out of the src folder plus binary artifacts go straight to war/WEB-INF/classes and war/WEB-INF/lib.

    2. Deploy once via $CATALINA_HOME/conf/Catalina/localhost/myapp.xml or make your project Dynamic Web Project in Eclipse.

    3. Start Tomcat in debug mode for hot-deployment of changes.

    ReplyDelete
  5. I'm using the Sysdeon Plugin's DevLoader and I'm really happy with that !!

    ReplyDelete
  6. Just use JRebel and you don't have these problems anymore: it's even better than hotdeploy.

    ReplyDelete
  7. +1 for JRebel, it works as well for classes and resources reloading, and handles well when you're using frameworks like Spring. A software every Java developer should use.

    ReplyDelete
  8. I'm using Maven + JRebel + STS with integrated Tomcat and Glassfish and I have none of the problems you mention.

    Caveat: JRebel will not always reload certain things like JAX-RS annotation changes. Plugins are in the works.

    ReplyDelete
  9. hye. I m deploying an application using tomcat and jsp .The jsp i ve placed my classes in WEB-INF/classes folder. one of the classes takes input from a application.xml file .where do i place the application .xml file

    ReplyDelete