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.)
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 ...
javax.<spec>
to jakarta.<spec>
for legal reasons.(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.