5

I have an example project here that uses Jetty to deploy a local server.

I use the mvn package exec:java command to run a local server, and it works fine. It loads HTML files, as well as content from servlets. Here are the pertinent files:

pom.xml

<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>io.happycoding</groupId>
  <artifactId>app-engine-hello-world</artifactId>
  <version>1</version>

  <properties>
    <!-- App Engine currently supports Java 11 -->
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <jetty.version>9.4.35.v20201120</jetty.version>

    <!-- Project-specific properties -->
    <exec.mainClass>io.happycoding.ServerMain</exec.mainClass>
    <googleCloudProjectId>YOUR_PROJECT_ID_HERE</googleCloudProjectId>
  </properties>

  <dependencies>
    <!-- Java Servlets API -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
    </dependency>

    <!-- Jetty -->
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-server</artifactId>
      <version>${jetty.version}</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-annotations</artifactId>
      <version>${jetty.version}</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <!-- Copy static resources like html files into the output jar file. -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <version>2.7</version>
        <executions>
          <execution>
            <id>copy-web-resources</id>
            <phase>compile</phase>
            <goals><goal>copy-resources</goal></goals>
            <configuration>
              <outputDirectory>
                ${project.build.directory}/classes/META-INF/resources
              </outputDirectory>
              <resources>
                <resource><directory>./src/main/webapp</directory></resource>
              </resources>
            </configuration>
          </execution>
        </executions>
      </plugin>

      <!-- Package everything into a single executable jar file. -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.2.4</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals><goal>shade</goal></goals>
            <configuration>
              <createDependencyReducedPom>false</createDependencyReducedPom>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <mainClass>${exec.mainClass}</mainClass>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>

      <!-- App Engine plugin for deploying to the live site. -->
      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>appengine-maven-plugin</artifactId>
        <version>2.2.0</version>
        <configuration>
          <projectId>${googleCloudProjectId}</projectId>
          <version>1</version>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

ServerMain.java

package io.happycoding;

import java.net.URL;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;

/**
 * Starts up the server, including a DefaultServlet that handles static files,
 * and any servlet classes annotated with the @WebServlet annotation.
 */
public class ServerMain {

  public static void main(String[] args) throws Exception {

    // Create a server that listens on port 8080.
    Server server = new Server(8080);
    WebAppContext webAppContext = new WebAppContext();
    server.setHandler(webAppContext);

    // Load static content from inside the jar file.
    URL webAppDir =
        ServerMain.class.getClassLoader().getResource("META-INF/resources");
    webAppContext.setResourceBase(webAppDir.toURI().toString());

    // Enable annotations so the server sees classes annotated with @WebServlet.
    webAppContext.setConfigurations(new Configuration[]{ 
      new AnnotationConfiguration(),
      new WebInfConfiguration(), 
    });

    // Look for annotations in the classes directory (dev server) and in the
    // jar file (live server)
    webAppContext.setAttribute(
        "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", 
        ".*/target/classes/|.*\\.jar");

    // Handle static resources, e.g. html files.
    webAppContext.addServlet(DefaultServlet.class, "/");

    // Start the server! 
    server.start();
    System.out.println("Server started!");

    // Keep the main thread alive while the server is running.
    server.join();
  }
}

HelloWorldServlet.java

package io.happycoding.servlets;

import java.io.IOException;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/hello")
public class HelloWorldServlet extends HttpServlet {

  @Override
  public void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException {
    response.setContentType("text/html;");
    response.getWriter().println("<h1>Hello world!</h1>");
  }
}

Again, this works fine... as long as I'm using Jetty 9.

But if I update my pom.xml file to set my jetty.version to 11.0.0, then suddenly I get a 404 for my servlet URL. I don't see any errors in the console.

What do I need to change about my code to get servlets to work again in Jetty 11?

(I'm asking because I got a bunch of dependabot updates to various student projects that all stopped working, and I'm trying to untangle them with the least friction possible.)

Kevin Workman
  • 41,537
  • 9
  • 68
  • 107
  • I'm making a guess here (since you don't have any errors), but could it be because Jetty 11 uses the JakartaEE namespace with a servlet version 5.0 and you are still using a `javax` servlet? You have `javax.servlet-api` in your dependencies for the `javax` classes, but maybe Jetty is looking for a `jakarta.servlet.http.HttpServlet` servlet? Again, it's just a guess. Also, have you tried using Jetty 10 to see if the code still works? (Jetty 10 uses servlet version 4.0) – Bogdan Feb 25 '21 at 09:47
  • @Bogdan Thanks for the reply. I believe you're onto something with the Jakarta suspicion, and that agrees with the answer below. I tried using Jetty 10, as well as the Jakarta servlets dependency (both with Jetty 10 and 11), but I'm still getting a 404. Looks like my only option is to go back to Jetty 9. – Kevin Workman Feb 26 '21 at 01:29
  • @KevinWorkman I also tried to update your app-engine-hello-world project to Jetty 11 without success. In case of the original project, exec:java clearly scans for the annotations but it won't scan after upgrade to Jetty 11. – MazeGen Jun 16 '21 at 11:19

1 Answers1

19

Jetty 11 is based on Jakarta Servlet 5.0, which is part of Jakarta EE 9.

Jakarta EE 9 underwent the "big bang" change (their name, not mine) to namespace and packaging, there is no longer a javax.servlet.* it is now jakarta.servlet.*.

There is literally nothing in Jetty 11 that looks for javax.servlet.*.

Some quick history ...

  • Oracle owned Java EE.
  • Oracle produced Java EE 7.
  • Oracle decided it didn't want to create/manage EE anymore.
  • Oracle gave all of EE to the Eclipse Foundation.
  • Oracle did not grant the Eclipse Foundation the right to use "java" or "javax" in this new EE reality.
  • Eclipse Foundation renamed it to "Jakarta EE" for legal reasons.
  • Eclipse Foundation releases "Jakarta EE 8" which is essentially just "Java EE 7" renamed for legal reasons (no package namespace change yet)
  • Eclipse Foundation renamed all packaging from javax.<spec> to jakarta.<spec> for legal reasons.
  • Eclipse Foundation releases "Jakarta EE 9" which is essentially just "Jakarta EE 8" but with a namespace change (this is the "big bang" mentioned above)

(be aware, I skimmed over a lot of other things that happened between these steps)

javax.servlet.* is dead, long live jakarta.servlet.*.

Jetty maintains the following versions (currently)

Jetty Servlet EE Namespace
Jetty 10.x Servlet 4.0 Jakarta EE 8 javax.servlet
Jetty 11.x Servlet 5.0 Jakarta EE 9 jakarta.servlet

There is no backward compatibility feature to allow both javax.servlet and jakarta.servlet to coexist in a release of Jetty. (we've tried this a few times, the complexity of the Servlet spec makes this very difficult for the HttpSession, RequestDispatcher, Dynamic servlet/filter registrations, etc). Jetty is working with the Servlet spec in Servlet 6.0 to attempt to address these concerns. A single release of Jetty supporting both namespaces is not likely until Jetty 12.

For now, the best we can hope for (and there are several projects started to do this, all alpha quality ATM) is some kind of tooling that updates your jars and/or source for the new packaging in an automated fashion to then be run on a Jakarta based server.

Joakim Erdfelt
  • 46,896
  • 7
  • 86
  • 136
  • 2
    In other words, fix Servlet API in `pom.xml` to use `jakarta.servlet:jakarta.servlet-api:5.0.0` instead and readjust code accordingly so it compiles. – BalusC Feb 25 '21 at 13:06
  • 1
    @BalusC and any of your other dependencies that use the servlet-api as well (in the OPs case, his pom seems to hint at using Google App Engine, which IIRC does not yet support jakarta.servlets) – Joakim Erdfelt Feb 25 '21 at 13:20
  • 2
    When I use `jakarta.servlet:jakarta.servlet-api:5.0.0` in my `pom.xml` file and change my servlet file to use the right imports, I'm still getting a 404. And I am indeed using App Engine to deploy my live site, so there might be further complications there. So it sounds like the right thing to do for now is to just go back to Jetty 9. Thank you both for the replies. I'm going to leave this open for a bit just in case anybody has a fix, but if not I'll accept this shortly. Thanks again! – Kevin Workman Feb 26 '21 at 01:23
  • 1
    @JoakimErdfelt I asked a follow-up question about Jetty 11 not detecting Jakarta servlets here: https://stackoverflow.com/questions/68058157/jetty-11-doesnt-detect-jakarta-servlets – Kevin Workman Jun 20 '21 at 17:00