Jetty 11 Doesn't Detect Servlets
Asked Answered
O

1

7

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

Oberammergau answered 25/2, 2021 at 3:11 Comment(3)
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) – Outrigger
@Outrigger 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. – Oberammergau
@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. – Apostil
S
24

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 Env Servlet EE Namespace
Jetty 12.x ee10 Servlet 6.0 Jakarta EE 10 jakarta.servlet
Jetty 12.x ee9 Servlet 5.0 Jakarta EE 9 jakarta.servlet
Jetty 12.x ee8 Servlet 4.0 Jakarta EE 8 javax.servlet
Jetty 11.x n/a Servlet 5.0 Jakarta EE 9 jakarta.servlet
Jetty 10.x n/a Servlet 4.0 Jakarta EE 8 javax.servlet

Starting with Jetty 12.x, Jetty supports the concept of Environments which allows for deploying webapps targeting different EE environments all at the same time on Jetty. This means you can have a webapp-legacy.war deployed to the ee8 environment that supports javax.servlet (on Servlet 4.0) and webapp-modern.war deployed to ee10 environment supporting jakarta.servlet (on Servlet 6.0)

Note: Jetty 11 and Jetty 10 do not have this environment functionality and are stuck on a fixed version of EE per their Jetty version.

Sandberg answered 25/2, 2021 at 12:30 Comment(4)
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. – Windle
@Windle 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) – Sandberg
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! – Oberammergau
@JoakimErdfelt I asked a follow-up question about Jetty 11 not detecting Jakarta servlets here: #68058657 – Oberammergau

© 2022 - 2024 β€” McMap. All rights reserved.