Beware of mvn war:inplace

So hopefully I can save somebody else four hours of frustrating, hair-tearing debugging... a few days ago I had to make a minor change to one of the webapps I maintain and redeploy it. I hadn't touched it in a while, but since I'm of course using Maven for build automation, I made the change, ran a mvn clean install and pushed the new .war out to my Tomcat cluster. I went to verify the change and - nothing. Hm. I must have overlooked something in the deployment step. So I did it again, being very careful to ensure that I had built from the correct directory, deployed what I had built... and still, same error. How strange.

I stared at the source directory. Was I in the right source directory? Check. Did I deploy the right .war? Check. I re-ran mvn clean by itself and verified that the target directory got wiped out. I ran mvn install and verified that the target directory had been re-created with new artifacts. I deployed. I checked the timestamps on the deployed artifact. Still, same error.

Now, this is getting frustrating. I copied the .war into /tmp and ran javap -c on the .class file to see exactly what was deployed. And, sure enough, what I had deployed did not include my fix. So, I triple checked that the .war that I had uploaded was the same one I had built. The timestamps matched. The file sizes matched. The MD5 checksum matched. It was the same file. So I decompiled the byte code on the artifact I had built. And lo and behold, I had built the old, unchanged code!

Well, now, the only way this could happen would be if I was building some old branch or something. Nope, src/main/java contained the code I was building. I rebuilt and decompiled the bytecode in target/main/classes. The decompiled byte code matched what I expected from the source code. So let me get this straight. target/main/classes contained my corrected code. The .war was being re-created on each build. But it contained the old code.

What. The. Hell. I ran mvn clean. I verified that the "target" directory was gone. Gone, gone, gone. I built and installed the package again. I looked at the target directory. It was new. Brand new. I checked the new .war, exploding and decompiling the contents again. It had built the old code.

However, there was an overlooked clue in mvn's output (you know, that output that you're supposed to be paying attention to...?)

[INFO] Processing war project
[INFO] Copying webapp resources [/Users/joshuadavies/devl/prod/app/src/main/webapp]
[INFO] Webapp assembled in [1428 msecs]
Now, of course, it's supposed to do that - that's where it gets web.xml from. But it triggered a memory from a while back... see, I had been working with a designer who needed to be able to style the .jsp artifacts in the project. Since he wasn't going to be making any code changes, it seemed like a lot of overhead for him to have to redeploy the project every time he wanted to make a change. So I did a little bit of research and discovered mvn war:inplace. This builds an exploded .war right in src/main/webapp for you — you can then point a local Tomcat instance to that directory and make (versionable) changes that are reflected in your running instance right away. This is nice, because you get the software engineering benefits of version control and build automation with a sped-up development cycle.

Well, since I'm the paranoid sort — and most of the changes I need to make are in servlet classes anyway, I re-build and re-deploy after each change, so once I had discovered mvn war:inplace, I showed him how to use it and went on with my development effort. What I didn't realize is that mvn clean doesn't remove that new src/main/webapp/WEB-INF/classes directory! So if you run mvn war:inplace make a change, and then run mvn clean install, Maven will dutifully compile all of your code, generate a new target directory, create an exploded .war in there (with the new code!), and then create a deployable artifact that includes the old build.

This seems like an oversight in the mvn war goal to me. As it turns out, there's a fix, but it's sort of an obscure one. If you add the following to your pom.xml:

<plugins>
    <plugin>
        <artifactId>maven-clean-plugin</artifactId>
        <version>2.4.1</version>
        <configuration>
            <filesets>
                <fileset>
                    <directory>src/main/webapp/WEB-INF/classes</directory>
                </fileset>
                <fileset>
                    <directory>src/main/webapp/WEB-INF/lib</directory>
                </fileset>
            </filesets>
        </configuration>
    </plugin>
</plugins>
Then the mvn clean task will clean up the leftover parts of mvn war:inplace each time it's run.

Add a comment:

Completely off-topic or spam comments will be removed at the discretion of the moderator.

You may preserve formatting (e.g. a code sample) by indenting with four spaces preceding the formatted line(s)

Name: Name is required
Email (will not be displayed publicly):
Comment:
Comment is required
Gleb Egunov, 2019-02-15
Great story! Thanks a lot, friend! It could be useful somewhen!))
rofelinhooo, 2021-05-26
Useful right now for me, but i'm already spent a couple of hours... Thanks a lot!
My Book

I'm the author of the book "Implementing SSL/TLS Using Cryptography and PKI". Like the title says, this is a from-the-ground-up examination of the SSL protocol that provides security, integrity and privacy to most application-level internet protocols, most notably HTTP. I include the source code to a complete working SSL implementation, including the most popular cryptographic algorithms (DES, 3DES, RC4, AES, RSA, DSA, Diffie-Hellman, HMAC, MD5, SHA-1, SHA-256, and ECC), and show how they all fit together to provide transport-layer security.

My Picture

Joshua Davies

Past Posts