- 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.
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.
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.
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.
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.