tomcat7-maven-plugin: Invalid byte tag in constant pool: 19

I use tomcat7-maven-plugin to spin up a Tomcat 7 container where I can run my web application. When I added dependencies for log4j2 (version 2.11.0) to my project, I got the error:

org.apache.tomcat.util.bcel.classfile.ClassFormatException:
Invalid byte tag in constant pool: 19

Apparently, log4j2 is a multi-release jar and older versions of Tomcat can’t handle that. So I needed to upgrade my Tomcat maven plugin.

Solution: Update your Tomcat

But how do you update your Tomcat? Its information page shows that it hasn’t been updated for a while, the latest version being 2.2 which runs 7.0.47 by default. Maven Central, on the other hand, shows that the latest version at the moment of this writing is 7.0.86. That’s the version we want.

Change your pom.xml in the following way:

<project>
  <properties>
    <tomcat.version>7.0.86</tomcat.version>
  </properties>
  <plugins>
    <plugin>
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <path>/</path>
          <port>7777</port>
        </configuration>
        <dependencies>
          <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-util</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-coyote</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-api</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-dbcp</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-servlet-api</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jsp-api</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jasper</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jasper-el</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-el-api</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-catalina</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-tribes</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-catalina-ha</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-annotations-api</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-juli</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-logging-juli</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
          <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-logging-log4j</artifactId>
            <version>${tomcat.version}</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugin>
  </plugins>
</project>

Using Groovy’s AntBuilder to zip and unzip files

Suppose we need to zip a bunch of Python files (without their compiled counterparts *.pyc). The next (admittedly contrived) example shows how we could go about doing something like this in Groovy:

File srcDir = new File('/home/neftas/Downloads/scripts/')
File destDir = new File(srcDir, 'antexample')
File zippedFile = new File(srcDir, 'antzipped.zip')

def allPythonFilenames = srcDir.list().sort().findAll { it.endsWith('.py') }
assert ['a.py', 'b.py', 'c.py'] == allPythonFilenames

def ant = new AntBuilder()

ant.with {
    echo 'begin zipping and unzipping'
    zip(destfile: zippedFile, basedir: srcDir, includes: '*.py')
    mkdir(dir: destDir)
    unzip(src: zippedFile, dest: destDir)
    echo 'done zipping and unzipping'
}

// zip file is created
assert zippedFile.exists()
// contents of zip file should match content of source directory
def commands = ['bash', '-c', "zipinfo -1 ${zippedFile.name}"]
def process = commands.execute(null, srcDir)
def contentsOfZip = process.text.split(System.lineSeparator)
assert contentsOfZip.sort() == allPythonFilenames

// all files should be unpacked from the zip into the right directory
assert destDir.list().sort() == allPythonFilenames

ant.with {
    echo 'deleting created files'
    // notice nested Ant task
    delete(includeEmptyDirs: true) {
        fileset(dir: destDir)
        fileset(file: zippedFile)
    }
    echo 'deleted created files'
}

We can use all the power of Ant in our Groovy scripts. Ant task names map to Groovy methods (see the zip method above) and the attributes are passed as maps to these methods. What’s even better, there is no need to stringify the arguments of these attributes (as is required in Ant’s XML), but we can directly use the correct datatypes (e.g. in the key-value pair destfile: zippedFile, zippedFile is of type java.util.File). All attributes of Ant tasks are available to use with AntBuilder (see Ant manual for zip task).

To use nested Ant tasks, we create closures, so that Ant’s:

<delete includeEmptyDirs="true">
    <fileset dir="lib">
    <fileset file="file.ext">
</delete>

results in the following Groovy code:

ant.delete(includeEmptyDirs: true) {
    fileset(dir: 'lib')
    fileset(file: 'file.ext')
}

Invoking from Gradle

AntBuilder is also available in Gradle. This will come in handy when you don’t know the "Gradle way" of doing something, but do know how to tackle it in Ant.

Dependencies

Groovy (and Gradle) come with a bundled version of AntBuilder, so you should be good shape when creating a Groovy script, but if you want to use AntBuilder in a project, you should add ant and ant-launcher to your build.gradle:

dependencies {
    compile group: 'ant', name: 'ant',  version: '1.7.0'
    compile group: 'ant', name: 'ant-launcher',  version: '1.6.5'
}

Adding a file to the root of your Gradle distribution

A while ago, I needed to write a program that retrieved some information from the database by using SQL queries located in files. I wanted to add a file that would contain the settings of the app, like the port, SID, login and password for the database, but also the location of those queries. The problem was I couldn’t add those settings to a file in the standard src/groovy/resources folder, because when creating a distribution with Gradle ./gradlew distZip the file would be part of the jar, and thus it would be difficult to modify the contents. I was basically after a run-control file, a settings file in the root of my distribution, that I could easily change on each run (if necessary).

Gradle provides several ways to accomplish this:

  1. Add the file to src/dist and the file will be put in the root of the distribution after invoking ./gradlew installDist. Any folders you create will also be put in the distribution, so you can create src/dist/config, for example, to put your configuration in, and a folder config will be created in the distribution.
  2. Indicate to Gradle that you want a certain file or folder copied:

    applicationDistribution.from('config.properties') {
        into ''
    }
    
  3. Use the distribution plugin:

    apply plugin: 'distribution'
    
    distributions {
        main {
            contents {
                from {
                    'settings.groovy'
                }
            }
        }
    }