5

This is a follow up to this question. I don't think it's a duplicate because the accepted answer indicates that Jetty 11 doesn't work with javax servlets, but I'm asking why Jetty 11 doesn't work with jakarta servlets.

I have an example project here that uses Jetty 9 to deploy a local server, including a javax servlet that uses the @WebServlet annotation. This all works fine.

Now I'm trying to update this to use Jetty 11 and the Jakarta servlets API.

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>jetty-hello-world</artifactId>
  <version>1</version>

  <properties>
    <!-- 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>11.0.5</jetty.version>
    <exec.mainClass>io.happycoding.ServerMain</exec.mainClass>
  </properties>

  <dependencies>
    <!-- Jakarta Servlets API -->
    <dependency>
      <groupId>jakarta.servlet</groupId>
      <artifactId>jakarta.servlet-api</artifactId>
      <version>5.0.0</version>
      <scope>provided</scope>
    </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>
    </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 jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.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>");
  }
}

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Jetty Hello World</title>
  </head>
  <body>
    <h1>Jetty Hello World</h1>
    <p>This is a sample HTML file. Click <a href="/hello">here</a> to see content served from a servlet.</p>
    <p>Learn more at <a href="https://happycoding.io">HappyCoding.io</a>.</p>
  </body>
</html>

I can run the server using mvn package exec:java and it starts up just fine.

My index.html file loads properly:

index.html

But when I navigate to my servlet's URL, I get a 404:

404

I don't see any errors (or any output at all) in my command line.

I should also mention that my Jakarta servlet works fine with the full version of Jetty 11 (using Jetty home and Jetty base directories, as described here). It's just this "standalone" Jetty server launcher that seems to be having trouble, so I'm suspicious of my ServerMain.java file.

What do I need to change to get Jetty 11 to detect my Jakarta servlet?

Kevin Workman
  • 41,537
  • 9
  • 68
  • 107
  • @ElliottFrisch The answer to [my previous question](https://stackoverflow.com/questions/66361859/jetty-11-doesnt-detect-servlets) states that Jetty 11 doesn't work with javax servlets. If Jetty 11 doesn't work with Jakarta servlets either, what *does* it work with? – Kevin Workman Jun 20 '21 at 17:11
  • @ElliottFrisch Are you saying that Jetty 11 doesn't support servlets at all? I appreciate the replies, but that doesn't match with anything else I've read so far. For [example](https://eclipse-foundation.blog/2021/03/24/eclipse-jetty-11-supports-the-big-bang/): "Eclipse Jetty 11 has been released and certified as compatible with the Jakarta Servlet v5.0 specification." Even the [documentation](https://www.eclipse.org/jetty/) you linked says "Jetty provides a web server and **servlet** container". – Kevin Workman Jun 20 '21 at 17:20
  • @ElliottFrisch `jakartaee.servlet` does not seem to be a Maven dependency. I am trying to read through the documentation, but everything I've read suggests that what I'm doing should work. I'm guessing there's something silly in my `ServerMain.java` file, but I'm not sure exactly what. I'm using Maven, and I'm not really worried about App Engine yet, just trying to get something working on my own machine first. But the latest version of App Engine supports arbitrary server containers, so my guess is if I can get this to work locally, it'll also work on App Engine. But that's not a priority. – Kevin Workman Jun 20 '21 at 17:42
  • @ElliottFrisch I should also mention that my Jakarta servlet works fine with the full version of Jetty 11 (using Jetty home and Jetty base directories). It's just this "standalone" Jetty server launcher that seems to be having trouble, which is why I'm suspicious of my `ServerMain.java` file. I'll edit my question to include that info. – Kevin Workman Jun 20 '21 at 17:55

1 Answers1

2

Remove the configuration effort, it's used wrong, and is the source of your issues.

These lines, delete them.

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

Starting with Jetty 10, you don't manage Configuration options this way. The existence of the jar with the feature in your classpath is enough. (in your case it's jetty-annotations-<ver>.jar)

Second, you don't ever use setConfigurations() with that small of a Configuration list (you are leaving out many required Configuration entries).

Instead, you should have used the addConfiguration(Configuration...) method, for just the new AnnotationConfiguration(), but only if that specific Configuration wasn't auto-discoverable, which jetty-annotation is.

Tip, use server.setDumpAfterStart(true) before your server.start() call, and see what the server state is, including what your WebAppContext looks like, and its Configuration entries. This is a great way to see what your changes are doing to your server and your contexts.

Here's an example project with Jetty 11, using @WebServlet, and WebAppContext.

https://github.com/jetty-project/embedded-servlet-server/blob/jetty-11.0.x/src/main/java/org/eclipse/jetty/demos/EmbedMe.java

Joakim Erdfelt
  • 46,896
  • 7
  • 86
  • 136