Programming

Jetty 12 ClassNotFoundException for ResourceServlet Fix

Resolve Jetty 12 ClassNotFoundException for org.eclipse.jetty.ee10.servlet.ResourceServlet on /webapp/* paths. Fix WebAppClassLoader isolation with parentLoaderPriority or bundle ee10-servlet JARs in web.xml for static resources in Java 21 WAR deployments.

1 answer 1 view

Jetty 12 ClassNotFoundException for org.eclipse.jetty.ee10.servlet.ResourceServlet when configuring for /webapp/* path

I’m running a Java 21 web application with static resources under the /webapp path. In Jetty 11, DefaultServlet worked fine, but Jetty now recommends ResourceServlet for non-root paths.

However, Jetty 12 fails to load ResourceServlet with a ClassNotFoundException. Here’s my configuration in /WEB-INF/web.xml:

xml
<servlet>
 <servlet-name>webapp-servlet</servlet-name>
 <servlet-class>org.eclipse.jetty.ee10.servlet.ResourceServlet</servlet-class>
 <init-param>
 <param-name>dirAllowed</param-name>
 <param-value>false</param-value>
 </init-param>
 <init-param>
 <param-name>baseResource</param-name>
 <param-value>webapp/</param-value>
 </init-param>
</servlet>

<servlet-mapping>
 <servlet-name>webapp-servlet</servlet-name>
 <url-pattern>/webapp/*</url-pattern>
</servlet-mapping>

After compiling to WAR and deploying to Jetty 12, startup fails with:

java.lang.ClassNotFoundException: org.eclipse.jetty.ee10.servlet.ResourceServlet
WARN :oejew.WebAppContext:main: Failed startup of context oeje10w.WebAppContext@{...}
jakarta.servlet.UnavailableException: Class loading error for holder webapp-servlet==org.eclipse.jetty.ee10.servlet.ResourceServlet@{... /webapp/WEB-INF/web.xml,STARTING}

I’ve confirmed org.eclipse.jetty.ee10.servlet.ResourceServlet is in the ee10-servlet module and included these Jetty 12 standard modules:

  • client
  • ee10-deploy
  • ee10-servlet
  • ext
  • http
  • resources
  • server

DefaultServlet works but triggers a warning about “invalid servlet mapping /webapp/*”.

What am I missing? How to correctly configure ResourceServlet in Jetty 12 for serving static resources from /webapp/*?

If you’re hitting a Jetty 12 ClassNotFoundException for org.eclipse.jetty.ee10.servlet.ResourceServlet on /webapp/* paths, it’s almost always the WebAppContext’s classloader hiding Jetty’s own classes from your WAR. Your web.xml config looks spot-on—baseResource=webapp/ and dirAllowed=false match the official docs—but Jetty 12’s stricter isolation needs a tweak like parentLoaderPriority=true or bundling the ee10-servlet JAR directly in WEB-INF/lib. This fixes the jakarta.servlet.UnavailableException at startup without warnings, letting you serve static resources cleanly from your Java 21 WAR.


Contents


Understanding Jetty 12 ClassNotFoundException for ResourceServlet

Ever deployed a WAR to Jetty 12 and watched it choke on java.lang.ClassNotFoundException: org.eclipse.jetty.ee10.servlet.ResourceServlet right at startup? You’re not alone. This hits folks migrating from Jetty 11, especially when swapping DefaultServlet for ResourceServlet on paths like /webapp/*. The error bubbles up as a jakarta.servlet.UnavailableException in the logs:

WARN :oejew.WebAppContext:main: Failed startup of context oeje10w.WebAppContext@{...}
jakarta.servlet.UnavailableException: Class loading error for holder webapp-servlet==org.eclipse.jetty.ee10.servlet.ResourceServlet@{...}

Your setup—Java 21, modules like ee10-servlet, http, resources—checks out on paper. Jetty 12 ships that class in the ee10-servlet module, as confirmed in the API docs. But during WAR deployment, the WebAppContext treats Jetty internals as off-limits. It’s a deliberate security/feature choice, not a bug. Short fix? Tell the classloader to look parent-first. We’ll get to that.

What makes this sneaky? Jetty 11 was looser; Jetty 12 (Jakarta EE 10 era) tightens the screws for better isolation in shared server setups. Your DefaultServlet fallback works but nags with “invalid servlet mapping /webapp/*—use ResourceServlet” warnings, per this GitHub thread.


Why ResourceServlet Replaces DefaultServlet

Why ditch DefaultServlet for non-root paths in Jetty 12? Simple: it’s optimized for root (/*), but subpaths like /webapp/* trigger those pesky warnings. Enter ResourceServlet—tailor-made for static resources under custom prefixes.

Compare them side-by-side:

Feature DefaultServlet ResourceServlet
Best for Root path /* Subpaths like /webapp/*
baseResource param Assumes root Flexible: webapp/, ./static/, etc.
Warnings on /webapp/* Yes, “invalid mapping” None
Jetty 12 Docs Rec Legacy for root only Preferred for static subdirs

From the ResourceServlet Javadoc, it’s a lightweight swap: same init-params (baseResource, dirAllowed, pathInfoOnly), but no root assumptions. Your config nails it—baseResource=webapp/ serves files from ./webapp/ relative to the context. DefaultServlet would scan the whole app root, which is overkill (and risky) for isolated static dirs.

Jetty devs pushed this in issues like #12476, urging migration. Stick with it; the ClassNotFoundException is just a classloader hiccup away from resolution.


Root Cause: WebAppClassLoader Isolation

Here’s the culprit: Jetty’s WebAppClassLoader. By default, it uses PARENT_LAST delegation—your WAR’s classes first, server classes last. Worse, WebAppContext hides Jetty packages entirely via DEFAULT_HIDDEN_CLASSES, including org.eclipse.jetty.ee10.servlet.*.

Peek at the WebAppContext docs: server classes like ResourceServlet live outside your WAR, in Jetty modules. But ee10-servlet isn’t bundled in WEB-INF/lib, so the isolated loader can’t see it. Boom—ClassNotFoundException.

Even with modules enabled (ee10-servlet, etc.), WAR deployment sandboxes them. Seen this before? GitHub issue #12100 reports identical ee10 class failures despite modules. It’s not missing deps; it’s deliberate isolation for multi-app servers. Question is, do you fight it or bundle?

Pros of isolation: Safer, no version clashes. Cons: Extra config for Jetty servlets in WARs.


Fix 1: Enable parentLoaderPriority

Quickest win—no JAR shuffling. Flip the classloader to PARENT_FIRST via context-param in web.xml. Jetty peeks server classes (including ResourceServlet) before your app.

Add this before your <servlet>:

xml
<context-param>
 <param-name>org.eclipse.jetty.server.webapp.parentLoaderPriority</param-name>
 <param-value>true</param-value>
</context-param>

Full effect? Startup scans Jetty modules first. From WebAppContext docs, this overrides hiding for your context. Works on Java 21, no restarts needed.

Alternative: JVM arg -Dorg.eclipse.jetty.server.webapp.parentLoaderPriority=true for global, but per-app is cleaner.

Test it: Redeploy WAR. No more UnavailableException. Drawback? Slightly less isolation, but fine for trusted WARs.


Fix 2: Bundle ee10-servlet Module in WEB-INF/lib

Prefer full isolation? Grab jetty-ee10-servlet-12.x.jar (and deps: jetty-http, jetty-resources) from Maven:

xml
<dependency>
 <groupId>org.eclipse.jetty.ee10</groupId>
 <artifactId>jetty-ee10-servlet</artifactId>
 <version>12.0.10</version> <!-- Match your Jetty -->
</dependency>
<dependency>
 <groupId>org.eclipse.jetty</groupId>
 <artifactId>jetty-http</artifactId>
 <version>12.0.10</version>
</dependency>

mvn package bundles them in WEB-INF/lib. Now ResourceServlet loads from your WAR—classloader happy.

Fix Pros Cons
parentLoaderPriority Zero build changes, fast Less isolation
Bundle JARs Total self-contained Bigger WAR, version sync

The ee10-servlet package summary lists exact deps. Matches your modules perfectly.


Complete web.xml Configuration for Jetty 12 Static Resources

Put it together. Here’s battle-tested web.xml for /webapp/*:

xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
 https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
 version="6.0">

 <!-- Fix classloader -->
 <context-param>
 <param-name>org.eclipse.jetty.server.webapp.parentLoaderPriority</param-name>
 <param-value>true</param-value>
 </context-param>

 <servlet>
 <servlet-name>webapp-servlet</servlet-name>
 <servlet-class>org.eclipse.jetty.ee10.servlet.ResourceServlet</servlet-class>
 <init-param>
 <param-name>baseResource</param-name>
 <param-value>webapp/</param-value>
 </init-param>
 <init-param>
 <param-name>dirAllowed</param-name>
 <param-value>false</param-value>
 </init-param>
 <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet-mapping>
 <servlet-name>webapp-servlet</servlet-name>
 <url-pattern>/webapp/*</url-pattern>
 </servlet-mapping>
</web-app>

Tweaks: Added load-on-startup for eager init. Handles 404s gracefully. For programmatic (no web.xml), see issue #11791:

java
ServletHolder holder = context.addServlet(ResourceServlet.class, "/webapp/*");
holder.setInitParameter("baseResource", "webapp/");

Testing and Troubleshooting

Deploy, hit http://localhost:8080/webapp/yourfile.css. 200 OK? Golden.

Stuck? Check:

  • Logs: Grep “UnavailableException” or “ClassNotFound”.
  • Modules: java -jar start.jar --list-modules confirms ee10-servlet.
  • Java 21: --add-opens if reflection gripes (rare).
  • 404s: Issue #12455 notes pathInfoOnly=true for clean aliases.

Edge case: Multi-context? Per-app parentLoaderPriority. Still warns? Nuke DefaultServlet entirely.

Pro tip: curl -v your path—headers reveal servlet in action.


Sources

  1. ResourceServlet — Official API for ResourceServlet configuration and init-params: https://javadoc.jetty.org/jetty-12/org/eclipse/jetty/ee10/servlet/ResourceServlet.html
  2. ee10-servlet Package Summary — Details on servlet modules and dependencies for Jetty 12: https://javadoc.jetty.org/jetty-12/org/eclipse/jetty/ee10/servlet/package-summary.html
  3. WebAppContext — Classloader configuration including parentLoaderPriority and hidden classes: https://javadoc.jetty.org/jetty-12/org/eclipse/jetty/ee10/webapp/WebAppContext.html
  4. Jetty Issue #12476 — Discussion of DefaultServlet warnings and ResourceServlet migration: https://github.com/jetty/jetty.project/issues/12476
  5. Jetty Issue #12100 — Reports of similar ClassNotFoundException for ee10 classes: https://github.com/jetty/jetty.project/issues/12100

Conclusion

Tame that Jetty 12 ClassNotFoundException for ResourceServlet by prioritizing the parent classloader or bundling JARs—your /webapp/* static serving will hum along warning-free. Start with parentLoaderPriority=true; it’s the least invasive for most setups. Once fixed, enjoy cleaner deployments on Java 21, ditching DefaultServlet legacy nag. Dive into the docs for tweaks, and you’re set.

Authors
Verified by moderation
Moderation
Jetty 12 ClassNotFoundException for ResourceServlet Fix