Category: Maven

  • New Jetty 12 Maven Coordinates

    Now that Jetty 12.0.1 is released to Maven Central, we’ve started to get a few questions about where some artifacts are, or when we intend to release them (as folks cannot find them).

    Things have change with Jetty, starting with the 12.0.0 release.

    First, is that our historical versioning of <servlet_support>.<major>.<minor> is no longer being used.

    With Jetty 12, we are now using a more traditional <major>.<minor>.<patch> versioning scheme for the first time.

    Also new in Jetty 12 is that the Servlet layer has been separated away from the Jetty Core layer.

    The Servlet layer has been moved to the new Environments concept introduced with Jetty 12.

    EnvironmentJakarta EEServletJakarta NamespaceJetty GroupID
    ee8EE84javax.servletorg.eclipse.jetty.ee8
    ee9EE95jakarta.servletorg.eclipse.jetty.ee9
    ee10EE106jakarta.servletorg.eclipse.jetty.ee10
    Jetty Environments

    This means the old Servlet specific artifacts have been moved to environment specific locations both in terms of Java namespace and also their Maven Coordinates.

    Example:

    Jetty 11 – Using Servlet 5
    Maven Coord: org.eclipse.jetty:jetty-servlet
    Java Class: org.eclipse.jetty.servlet.ServletContextHandler

    Jetty 12 – Using Servlet 6
    Maven Coord: org.eclipse.jetty.ee10:jetty-ee10-servlet
    Java Class: org.eclipse.jetty.ee10.servlet.ServletContextHandler

    We have a migration document which lists all of the migrated locations from Jetty 11 to Jetty 12.

    This new versioning and environment features built into Jetty means that new major versions of Jetty are not as common as they have been in the past.




  • Building Jetty with JDK 9

    The Jetty Project has been trying to to build Jetty using JDK 9 for some time now.
    We still have a number of things to fix (a few test cases and integration with ASM for class scanning), but overall we have reached a point where we can build Jetty with JDK 9 and will start using JDK 9 to build Jetty releases.
    This process started with our first attempts using older JDK 9 releases, and the results were ugly. Until recently the Maven Plugins required to build Jetty were not updated to work with JDK 9, so trying to build Jetty with JDK 9 was not possible. After most of the Maven Plugins released versions compatible with JDK 9, fiddling with MAVEN_OPTS and adding a number of --add-opens we had our first success, but it was not pretty.
    Fast forward to the JDK 9 release candidate (JDK 9+181) and we could build Jetty without too much effort, mostly thanks to the latest changes applied to the JDK module system. Below is a report of what we had to do, which will hopefully be of use to others.
    The Jetty Project is a multi-module Maven project where we want to maintain JDK 8 compatibility, but we also want to build 2 modules that are JDK 9-specific (the client and server JDK 9 ALPN implementations).
    First and foremost, you’ll want to use the latest versions of the Maven Plugins, especially the maven-compiler-plugin (as of today at 3.6.2) and the maven-javadoc-plugin (as of today at 3.0.0-M1). Make sure you have them all declared in a <pluginManagement> section, so that you specify their version only there (and only once).
    It’s fairly easy to build a module only when building with JDK 9:

    <project ...>
      ...
      <profiles>
        <profile>
          <id>jdk9</id>
          <activation>
            <jdk>[1.9,)</jdk>
          </activation>
          <modules>
            <module>jdk9-specific-module</module>
          </modules>
        </profile>
      </profiles>
    </project
    

    Next, we want to target JDK 8 for all other modules. This is typically done by configuring the maven-compiler-plugin, specifying <target>1.8</target>.
    It turns out that this is not enough though, because while the JDK 9 compiler will produce class files compatible with JDK 8, it will compile against a JDK 9 runtime with the risk that a JDK 9 only class or method was used by mistake in the source code. Even if you are careful and you don’t use JDK 9 classes or methods in your source code, you can still hit binary incompatibilities that exist between JDK 8 and JDK 9.
    Take, for example, the class java.nio.ByteBuffer.
    In JDK 8 it inherits method limit(int) from the parent class, java.nio.Buffer, and the exact signature is:

    public final Buffer limit(int newLimit)
    

    In JDK 9 this has changed; method limit(int) is now overridden in java.nio.ByteBuffer to covariantly return java.nio.ByteBuffer, so the signature is now:

    public ByteBuffer limit(int newLimit)
    

    The difference in return type causes a binary incompatibility, so that if you compile a class that uses ByteBuffer.limit(int) with JDK 9 and try to run with JDK 8 it fails with:

    java.lang.NoSuchMethodError: java.nio.ByteBuffer.limit(I)Ljava/nio/ByteBuffer;
    

    Indeed, JDK 8 does not have that method with that specific signature.
    Luckily, the JDK 9 compiler has a new switch, --release, that allows to easily target previous JDKs (as specified by JEP 247).
    JDK 9 comes with a file ($JDK_HOME/lib/ct.sym) that is a zipped file (you can view its content in a normal file manager) containing directories for the supported target platforms (JDK 6, 7, 8 and 9), and each directory contains all the symbols for that specific JDK platform.
    The latest maven-compiler-plugin supports this new compiler switch, so compiling correctly is now just a matter of simple configuration:

    <project ...>
      <pluginManagement>
        <plugins>
          <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.6.2</version>
            <configuration>
              <source>1.8</source>
              <target>1.8</target>
            </configuration>
          </plugin>
        </plugins>
      </pluginManagement>
      <profiles>
        <profile>
          <id>jdk9</id>
          <activation>
            <jdk>[1.9,)</jdk>
          </activation>
          <build>
            <plugins>
              <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                  <release>8</release>
                </configuration>
              </plugin>
            </plugins>
          </build>
        </profile>
      </profiles>
    </project>
    

    In the <pluginManagement> section, we configure the maven-compiler-plugin with source and target to be JDK 8. This section will be taken into account when compiling using JDK 8.
    In the profile, activated only when compiling using JDK 9, we configure the maven-compiler-plugin with release to be JDK 8, so that the JDK 9 compiler generates the right class files using the symbols for JDK 8 contained in ct.sym.
    Finally, we uncovered a strange issue that we think is a Javadoc bug, apparently also hit by the Lucene Project (see https://github.com/eclipse/jetty.project/issues/1741). For us the workaround was simple, just converting an anonymous inner class into a named inner class, but your mileage may vary.
    Hopefully this blog will help you build your projects with Maven and JDK 9.

  • Simple Jetty HelloWorld Webapp

    With the jetty-maven-plugin and Servlet Annotations, it has never been simpler to start developing with Jetty! While we have not quiet achieved the terseness of some convention over configuration environments/frameworks/languages, it is getting close and only 2 files are needed to run a web application!

    Maven pom.xml

    A minimal maven pom.xml is need to declare a dependency on the Servlet API and use the jetty-maven-plugin.   A test project and pom.xml can be created with:

    $ mkdir demo-webapp
    $ cd demo-webapp
    $ gedit pom.xml

    The pom.xml file is still a little verbose and the minimal file needs to be at least:

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
       <modelVersion>4.0.0</modelVersion>
       <packaging>war</packaging>
       <groupId>org.eclipse.jetty.demo</groupId>
       <artifactId>jetty-helloworld-webapp</artifactId>
       <version>1.0</version>
       <dependencies>
         <dependency>
           <groupId>javax.servlet</groupId>
           <artifactId>javax.servlet-api</artifactId>
           <version>3.1.0</version>
           <scope>provided</scope>
         </dependency>
       </dependencies>
       <build>
         <plugins>
           <plugin>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-maven-plugin</artifactId>
             <version>9.4.5.v20170502</version>
           </plugin>
           <plugin>
             <groupId>org.apache.maven.plugins</groupId>
             <artifactId>maven-war-plugin</artifactId>
             <version>3.0.0</version>
             <configuration>
               <failOnMissingWebXml>false</failOnMissingWebXml>
             </configuration>
           </plugin>
         </plugins>
       </build>
    </project>

    Annotated HelloWorld Servlet

    Maven conventions for Servlet development are satisfied by creating the Servlet code in following source directory:

    $ mkdir -p src/main/java/com/example
    $ gedit src/main/java/com/example/HelloWorldServlet.java

    Annotations allows for a very simple Servlet file that is mostly comprised of imports:

    package com.example;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    @WebServlet(urlPatterns = {"/*"}, loadOnStartup = 1)
    public class HelloWorldServlet extends HttpServlet
    {
     @Override
     public void doGet(HttpServletRequest request, HttpServletResponse response)
     throws IOException
     {
     response.getOutputStream().print("Hello World");
     }
    }

    Running the Web Application

    All that is left to do is to run the web application:

    $ mvn jetty:run
    

    You can then point your browser at http://localhost:8080/ to see your web application!

    Next Steps

    OK, not the most exciting web application, but it is a start.  From here you could:

    • Clone this demo from github.
    • Add more Servlets or some Filters
    • Add static content in the src/main/webapp directory
    • Create a web deployment descriptor in src/main/webapp/WEB-INF/web.xml
    • Build a war file with mvn install
  • mvn jetty:run-forked

    Being able to run the jetty maven plugin on your webapp – but in a freshly forked jvm – is a feature that has been requested for a loooong time. With jetty-7.5.2 release, this feature has been implemented, and it even works on your unassembled webapp.

    How to Run


    mvn jetty:run-forked

    That will kick off a Jetty instance in a brand new jvm and deploy your unassemabled webapp to it. The forked Jetty will keep on running until either:

    • you execute a mvn jetty:stop (in another terminal window)
    • you <cntrl-c> the plugin

    The plugin will keep on executing until either:

    • you stop it with a <cntrl-c>
    • the forked jvm terminates

    NOTE: I’m interested in obtaining feedback about the lifecycles of the plugin and the forked Jetty. Is the lifecycle linkage that I’ve implemented the way you want to use it? Do you want the forked jvm to continue on, even if the plugin exits? Please post your input to the Jetty list at jetty-users@eclipse.org.

    How to Configure

    You need a few different configuration parameters from the usual jetty:run ones. Let’s look at an example:

         <plugin>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>jetty-maven-plugin</artifactId>
            <version>7.5.2.v20111006</version>
            <configuration>
              <stopPort>8087</stopPort>
              <stopKey>foo</stopKey>
              <jettyXml>src/main/config/jetty.xml</jetty.xml>
              <contextXml>src/main/config/context.xml</jetty.xml>
              <contextPath>/foo</contextPath>
              <tmpDirectory>${project.build.directory}/tmp</tmpDirectory>
              <jvmArgs>-verbose:gc -Xmx80m</jvmArgs>
            </configuration>
          </plugin>
    

    You need to specify the stopKey and stopPort so that you can control the forked Jetty using the handy maven goal mvn jetty:stop.
    You can use the jettyXml parameter to specify a comma separated list of jetty xml configuration files that you can use to configure the container. There’s nothing special about these config files, they’re just normal jetty configuration files. You can also use this parameter with the jetty:run goal too.
    The contextXml parameter specifies the location of a webapp context xml configuration file. Again, this is a normal jetty context xml configuration file. You can also use this with the jetty:run goal too, either in conjunction with, or instead of, the <webAppConfig> parameter (which configures the webapp right there in the pom). As the jetty:run-forked goal does NOT support the <webAppConfig> element, you MUST use contextXml if you need to configure the webapp.
    The contextPath parameter specifies the context path at which to deploy the webapp. You can use this as a simple shortcut instead of the contextXml parameter if you have no other configuration that you need to do for the webapp. Or, you can specify both this AND the contextXml parameter, in which case the contextPath takes precedence over the context path inside the context xml file.
    tmpDirectory is the location of a temporary working directory for the webapp. You can configure it either here, or in a contextXml file. If specified in both places, the tmpDirectory takes precedence.
    With the jvmArgs parameter, you can specify an arbitrary list of args that will be passed as-is to the newly forked jvm.
    There’s also the same parameters as the mvn jetty:run goal:

    • skip – if true the execution of the plugin is skipped
    • useTestScope – if true, jars of <scope>test</scope> and the test classes are placed on the webapp’s classpath inside the forked jvm
    • useProvidedScope – if true, jars of <scope>provided</scope> are placed on the container’s classpath inside the forked jvm
    • classesDirectory – the location of the classes for the webapp
    • testClassesDirectory – the location of the test classes
    • webAppSourceDirectory – the location of the static resources for the webapp

    Also, just like the mvn jetty:run case, if you have dependencies that are <type>war</type> , then their resources will be overlaid onto the webapp when it is deployed in the new jvm.