Migrating from Jetty 11.0.x to Jetty 12.0.x

Required Java Version Changes

Jetty 11.0.x Jetty 12.0.x

Java 11

Java 17

Maven Artifacts Changes

Jetty 11.0.x Jetty 12.0.x

org.eclipse.jetty.fcgi:fcgi-client

org.eclipse.jetty.fcgi:jetty-fcgi-client

org.eclipse.jetty.fcgi:fcgi-server

org.eclipse.jetty.fcgi:jetty-fcgi-server

org.eclipse.jetty.http2:http2-client

org.eclipse.jetty.http2:jetty-http2-client

org.eclipse.jetty.http2:http2-common

org.eclipse.jetty.http2:jetty-http2-common

org.eclipse.jetty.http2:http2-hpack

org.eclipse.jetty.http2:jetty-http2-hpack

org.eclipse.jetty.http2:http2-http-client-transport

org.eclipse.jetty.http2:jetty-http2-client-transport

org.eclipse.jetty.http2:http2-server

org.eclipse.jetty.http2:jetty-http2-server

org.eclipse.jetty.http3:http3-client

org.eclipse.jetty.http3:jetty-http3-client

org.eclipse.jetty.http3:http3-common

org.eclipse.jetty.http3:jetty-http3-common

org.eclipse.jetty.http3:http3-http-client-transport

org.eclipse.jetty.http3:jetty-http3-client-transport

org.eclipse.jetty.http3:http3-qpack

org.eclipse.jetty.http3:jetty-http3-qpack

org.eclipse.jetty.http3:http3-server

org.eclipse.jetty.http3:jetty-http3-server

org.eclipse.jetty:jetty-osgi.*

  • org.eclipse.jetty:jetty-osgi

  • org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-osgi-*

org.eclipse.jetty:jetty-proxy

  • org.eclipse.jetty:jetty-proxy

  • org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-proxy

org.eclipse.jetty.quic:quic-client

org.eclipse.jetty.quic:jetty-quic-client

org.eclipse.jetty.quic:quic-common

org.eclipse.jetty.quic:jetty-quic-common

org.eclipse.jetty.quic:quic-quiche

org.eclipse.jetty.quic:jetty-quic-quiche

org.eclipse.jetty.quic:quic-server

org.eclipse.jetty.quic:jetty-quic-server

org.eclipse.jetty:jetty-unixsocket.*

Removed — Use org.eclipse.jetty:jetty-unixdomain-server

org.eclipse.jetty.websocket:websocket-core-client

org.eclipse.jetty.websocket:jetty-websocket-core-client

org.eclipse.jetty.websocket:websocket-core-common

org.eclipse.jetty.websocket:jetty-websocket-core-common

org.eclipse.jetty.websocket:websocket-core-server

org.eclipse.jetty.websocket:jetty-websocket-core-server

org.eclipse.jetty.websocket:websocket-jetty-api

org.eclipse.jetty.websocket:jetty-websocket-jetty-api

org.eclipse.jetty.websocket:websocket-jetty-client

  • org.eclipse.jetty.websocket:jetty-websocket-jetty-client

  • org.eclipse.jetty.ee{8,9,10}.websocket:jetty-ee{8,9,10}-websocket-jetty-client

org.eclipse.jetty.websocket:websocket-jetty-common

  • org.eclipse.jetty.websocket:jetty-websocket-jetty-common

  • org.eclipse.jetty.ee{8,9,10}.websocket:jetty-ee{8,9,10}-websocket-jetty-common

org.eclipse.jetty.websocket:websocket-jetty-server

  • org.eclipse.jetty.websocket:jetty-websocket-jetty-server

  • org.eclipse.jetty.ee{8,9,10}.websocket:jetty-ee{8,9,10}-websocket-jetty-server

org.eclipse.jetty.websocket:websocket-jakarta-client

org.eclipse.jetty.ee{8,9,10}.websocket:jetty-ee{8,9,10}-websocket-jakarta-client

org.eclipse.jetty.websocket:websocket-jakarta-common

org.eclipse.jetty.ee{8,9,10}.websocket:jetty-ee{8,9,10}-websocket-jakarta-common

org.eclipse.jetty.websocket:websocket-jakarta-server

org.eclipse.jetty.ee{8,9,10}.websocket:jetty-ee{8,9,10}-websocket-jakarta-server

org.eclipse.jetty.websocket:websocket-servlet

org.eclipse.jetty.ee{8,9,10}.websocket:jetty-ee{8,9,10}-websocket-servlet

org.eclipse.jetty:apache-jsp

org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-apache-jsp

org.eclipse.jetty:jetty-annotations

org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-annotations

org.eclipse.jetty:jetty-ant

Removed — No Replacement

org.eclipse.jetty:jetty-cdi

org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-cdi

org.eclipse.jetty:glassfish-jstl

org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-glassfish-jstl

org.eclipse.jetty:jetty-jaspi

org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-jaspi

org.eclipse.jetty:jetty-jndi

org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-jndi

org.eclipse.jetty:jetty-jspc-maven-plugin

org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-jspc-maven-plugin

org.eclipse.jetty:jetty-maven-plugin

org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-maven-plugin

org.eclipse.jetty:jetty-plus

org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-plus

org.eclipse.jetty:jetty-quickstart

org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-quickstart

org.eclipse.jetty:jetty-runner

org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-runner

org.eclipse.jetty:jetty-servlet

org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-servlet

org.eclipse.jetty:jetty-servlets

org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-servlets

org.eclipse.jetty:jetty-webapp

org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-webapp

Class Packages/Names Changes

Jetty 11.0.x Jetty 12.0.x

org.eclipse.jetty.client.api.*

org.eclipse.jetty.client.*

org.eclipse.jetty.client.util.*

org.eclipse.jetty.client.*

org.eclipse.jetty.client.util.*

org.eclipse.jetty.client.*

org.eclipse.jetty.client.http.*

org.eclipse.jetty.client.transport.*

org.eclipse.jetty.http2.client.http.*

org.eclipse.jetty.http2.client.transport.*

org.eclipse.jetty.websocket.api.annotation.OnWebSocketConnect

org.eclipse.jetty.websocket.api.annotation.OnWebSocketOpen

org.eclipse.jetty.websocket.api.WriteCallback

org.eclipse.jetty.websocket.api.Callback

org.eclipse.jetty.websocket.api.WebSocket*Listener

org.eclipse.jetty.websocket.api.Session.Listener.AutoDemanding

org.eclipse.jetty.websocket.api.RemoteEndpoint

org.eclipse.jetty.websocket.api.Session

org.eclipse.jetty.websocket.api.WebSocketPolicy

org.eclipse.jetty.websocket.api.Configurable

Migrate Servlets to Jetty Handlers

Web applications written using the Servlet APIs may be re-written using the Jetty Handler APIs. The sections below outline the Jetty Handler APIs that correspond to the Servlet APIs. For more information about why using the Jetty Handler APIs instead of the Servlet APIs, refer to this section.

For more information about replacing HttpServlets or Servlet Filters with Jetty Handlers, refer to this section.

Handler Request APIs

public class RequestAPIs extends Handler.Abstract
{
    @Override
    public boolean handle(Request request, Response response, Callback callback) throws Exception
    {
        // Gets the request method.
        // Replaces:
        //   - servletRequest.getMethod();
        String method = request.getMethod();

        // Gets the request protocol name and version.
        // Replaces:
        //   - servletRequest.getProtocol();
        String protocol = request.getConnectionMetaData().getProtocol();

        // Gets the request URL.
        // Replaces:
        //   - servletRequest.getRequestURL();
        HttpURI httpURI = HttpURI.build(request.getHttpURI()).query(null);
        StringBuffer requestURL = new StringBuffer(httpURI.asString());

        // Gets the request context.
        // Replaces:
        //   - servletRequest.getServletContext()
        Context context = request.getContext();

        // Gets the context path.
        // Replaces:
        //   - servletRequest.getContextPath()
        String contextPath = context.getContextPath();

        // Gets the request path.
        // Replaces:
        //   - servletRequest.getRequestURI();
        String requestPath = request.getHttpURI().getPath();

        // Gets the request path after the context path.
        // Replaces:
        //   - servletRequest.getServletPath() + servletRequest.getPathInfo()
        String pathInContext = Request.getPathInContext(request);

        // Gets the request query.
        // Replaces:
        //   - servletRequest.getQueryString()
        String queryString = request.getHttpURI().getQuery();

        // Gets request parameters.
        // Replaces:
        //   - servletRequest.getParameterNames();
        //   - servletRequest.getParameter(name);
        //   - servletRequest.getParameterValues(name);
        //   - servletRequest.getParameterMap();
        Fields queryParameters = Request.extractQueryParameters(request, UTF_8);
        Fields allParameters = Request.getParameters(request);

        // Gets cookies.
        // Replaces:
        //   - servletRequest.getCookies();
        List<HttpCookie> cookies = Request.getCookies(request);

        // Gets request HTTP headers.
        // Replaces:
        //   - servletRequest.getHeaderNames()
        //   - servletRequest.getHeader(name)
        //   - servletRequest.getHeaders(name)
        //   - servletRequest.getDateHeader(name)
        //   - servletRequest.getIntHeader(name)
        HttpFields requestHeaders = request.getHeaders();

        // Gets the request Content-Type.
        // Replaces:
        //   - servletRequest.getContentType()
        String contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE);

        // Gets the request Content-Length.
        // Replaces:
        //   - servletRequest.getContentLength()
        //   - servletRequest.getContentLengthLong()
        long contentLength = request.getLength();

        // Gets the request locales.
        // Replaces:
        //   - servletRequest.getLocale()
        //   - servletRequest.getLocales()
        List<Locale> locales = Request.getLocales(request);

        // Gets the request scheme.
        // Replaces:
        //   - servletRequest.getScheme()
        String scheme = request.getHttpURI().getScheme();

        // Gets the server name.
        // Replaces:
        //   - servletRequest.getServerName()
        String serverName = Request.getServerName(request);

        // Gets the server port.
        // Replaces:
        //   - servletRequest.getServerPort()
        int serverPort = Request.getServerPort(request);

        // Gets the remote host/address.
        // Replaces:
        //   - servletRequest.getRemoteAddr()
        //   - servletRequest.getRemoteHost()
        String remoteAddress = Request.getRemoteAddr(request);

        // Gets the remote port.
        // Replaces:
        //   - servletRequest.getRemotePort()
        int remotePort = Request.getRemotePort(request);

        // Gets the local host/address.
        // Replaces:
        //   - servletRequest.getLocalAddr()
        //   - servletRequest.getLocalHost()
        String localAddress = Request.getLocalAddr(request);

        // Gets the local port.
        // Replaces:
        //   - servletRequest.getLocalPort()
        int localPort = Request.getLocalPort(request);

        // Gets the request attributes.
        // Replaces:
        //   - servletRequest.getAttributeNames()
        //   - servletRequest.getAttribute(name)
        //   - servletRequest.setAttribute(name, value)
        //   - servletRequest.removeAttribute(name)
        String name = "name";
        Object value = "value";
        Set<String> names = request.getAttributeNameSet();
        Object attribute = request.getAttribute(name);
        Object oldValue = request.setAttribute(name, value);
        Object removedValue = request.removeAttribute(name);
        request.clearAttributes();
        Map<String, Object> map = request.asAttributeMap();

        // Gets the request trailers.
        // Replaces:
        //   - servletRequest.getTrailerFields()
        HttpFields trailers = request.getTrailers();

        // Gets the HTTP session.
        // Replaces:
        //   - servletRequest.getSession()
        //   - servletRequest.getSession(create)
        boolean create = true;
        Session session = request.getSession(create);

        callback.succeeded();
        return false;
    }
}

Handler Request Content APIs

@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
    // Non-blocking read the request content as a String.
    // Use with caution as the request content may be large.
    CompletableFuture<String> completable = Content.Source.asStringAsync(request, UTF_8);

    completable.whenComplete((requestContent, failure) ->
    {
        if (failure == null)
        {
            // Process the request content here.

            // Implicitly respond with status code 200 and no content.
            callback.succeeded();
        }
        else
        {
            // Implicitly respond with status code 500.
            callback.failed(failure);
        }
    });

    return true;
}

@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
    // Non-blocking read the request content as a ByteBuffer.
    // Use with caution as the request content may be large.
    CompletableFuture<ByteBuffer> completable = Content.Source.asByteBufferAsync(request);

    completable.whenComplete((requestContent, failure) ->
    {
        if (failure == null)
        {
            // Process the request content here.

            // Implicitly respond with status code 200 and no content.
            callback.succeeded();
        }
        else
        {
            // Implicitly respond with status code 500.
            callback.failed(failure);
        }
    });

    return true;
}

@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
    // Read the request content as an InputStream.
    // Note that InputStream.read() may block.
    try (InputStream inputStream = Content.Source.asInputStream(request))
    {
        while (true)
        {
            int read = inputStream.read();

            // EOF was reached, stop reading.
            if (read < 0)
                break;

            // Process the read byte here.
        }
    }

    // Implicitly respond with status code 200 and no content.
    callback.succeeded();
    return true;
}

@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
    CompletableTask<Void> reader = new CompletableTask<>()
    {
        @Override
        public void run()
        {
            // Read in a loop.
            while (true)
            {
                // Read a chunk of content.
                Content.Chunk chunk = request.read();

                // If there is no content, demand to be
                // called back when more content is available.
                if (chunk == null)
                {
                    request.demand(this);
                    return;
                }

                // If a failure is read, complete with a failure.
                if (Content.Chunk.isFailure(chunk))
                {
                    Throwable failure = chunk.getFailure();
                    completeExceptionally(failure);
                    return;
                }

                if (chunk instanceof Trailers trailers)
                {
                    // Possibly process the request trailers here.
                    // Trailers have an empty ByteBuffer and are a last chunk.
                }

                // Process the request content chunk here.
                // After the processing, the chunk MUST be released.
                chunk.release();

                // If the last chunk is read, complete normally.
                if (chunk.isLast())
                {
                    complete(null);
                    return;
                }

                // Not the last chunk of content, loop around to read more.
            }
        }
    };

    // Initiate the read of the request content.
    reader.start();

    // When the read is complete, complete the Handler callback.
    callback.completeWith(reader);

    return true;
}

Handler Response APIs

public class ResponseAPIs extends Handler.Abstract
{
    @Override
    public boolean handle(Request request, Response response, Callback callback) throws Exception
    {
        // Sets/Gets the response HTTP status.
        // Replaces:
        //   - servletResponse.setStatus(code);
        //   - servletResponse.getStatus();
        response.setStatus(HttpStatus.OK_200);
        int status = response.getStatus();

        // Gets the response HTTP headers.
        // Replaces:
        //   - servletResponse.setHeader(name, value);
        //   - servletResponse.addHeader(name, value);
        //   - servletResponse.setDateHeader(name, date);
        //   - servletResponse.addDateHeader(name, date);
        //   - servletResponse.setIntHeader(name, value);
        //   - servletResponse.addIntHeader(name, value);
        //   - servletResponse.getHeaderNames()
        //   - servletResponse.getHeader(name)
        //   - servletResponse.getHeaders(name)
        //   - servletResponse.containsHeader(name)
        HttpFields.Mutable responseHeaders = response.getHeaders();

        // Sets an HTTP cookie.
        // Replaces:
        //   - Cookie cookie = new Cookie("name", "value");
        //   - cookie.setDomain("example.org");
        //   - cookie.setPath("/path");
        //   - cookie.setMaxAge(24 * 3600);
        //   - cookie.setAttribute("SameSite", "Lax");
        //   - servletResponse.addCookie(cookie);
        HttpCookie cookie = HttpCookie.build("name", "value")
            .domain("example.org")
            .path("/path")
            .maxAge(Duration.ofDays(1).toSeconds())
            .sameSite(HttpCookie.SameSite.LAX)
            .build();
        Response.addCookie(response, cookie);

        // Sets the response Content-Type.
        // Replaces:
        //   - servletResponse.setContentType(type)
        responseHeaders.put(HttpHeader.CONTENT_TYPE, "text/plain; charset=UTF-8");

        // Sets the response Content-Length.
        // Replaces:
        //   - servletResponse.setContentLength(length)
        //   - servletResponse.setContentLengthLong(length)
        responseHeaders.put(HttpHeader.CONTENT_LENGTH, 1024L);

        // Sets/Gets the response trailers.
        // Replaces:
        //   - servletResponse.setTrailerFields(() -> trailers)
        //   - servletResponse.getTrailerFields()
        HttpFields trailers = HttpFields.build().put("checksum", 0xCAFE);
        response.setTrailersSupplier(trailers);
        Supplier<HttpFields> trailersSupplier = response.getTrailersSupplier();

        // Gets whether the response is committed.
        // Replaces:
        //   - servletResponse.isCommitted()
        boolean committed = response.isCommitted();

        // Resets the response.
        // Replaces:
        //   - servletResponse.reset();
        response.reset();

        // Sends a redirect response.
        // Replaces:
        //   - servletResponse.encodeRedirectURL(location)
        //   - servletResponse.sendRedirect(location)
        String location = Request.toRedirectURI(request, "/redirect");
        Response.sendRedirect(request, response, callback, location);

        // Sends an error response.
        // Replaces:
        //   - servletResponse.sendError(code);
        //   - servletResponse.sendError(code, message);
        Response.writeError(request, response, callback, HttpStatus.SERVICE_UNAVAILABLE_503, "Request Cannot be Processed");

        callback.succeeded();
        return true;
    }
}

Handler Response Content APIs

@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
    // Produces an implicit response with status code 200
    // with no content when returning from this method.

    // The Handler callback must be completed when returning true.
    callback.succeeded();
    return true;
}

@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
    // Produces an implicit response with status 204
    // with no content when returning from this method.
    response.setStatus(HttpStatus.NO_CONTENT_204);

    // The Handler callback must be completed when returning true.
    callback.succeeded();
    return true;
}

@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
    // Produces an explicit response with status 204 with no content.
    response.setStatus(HttpStatus.NO_CONTENT_204);

    // This explicit first write() writes the response status code and headers.
    // It is also the last write (as specified by the first parameter)
    // and writes an empty content (the second parameter, a null ByteBuffer).
    // When this write completes, the Handler callback is completed.
    response.write(true, null, callback);

    return true;
}

@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
    response.setStatus(HttpStatus.OK_200);

    ByteBuffer content = UTF_8.encode("Hello World");

    // Explicit first write that writes the response status code, headers and content.
    // When this write completes, the Handler callback is completed.
    response.write(true, content, callback);

    return true;
}
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
    response.setStatus(HttpStatus.OK_200);

    ByteBuffer content = UTF_8.encode("Hello World");
    response.getHeaders().put(HttpHeader.CONTENT_LENGTH, content.remaining());

    // Flush the response status code and the headers (no content).
    // This is the fist but non-last write.
    Callback.Completable completable = new Callback.Completable();
    response.write(false, null, completable);

    // When the first write completes, perform the second (and last) write.
    completable.whenComplete((ignored, failure) ->
    {
        if (failure == null)
        {
            // Now explicitly write the content as the last write.
            // When this write completes, the Handler callback is completed.
            response.write(true, content, callback);
        }
        else
        {
            // Implicitly respond with status code 500.
            callback.failed(failure);
        }
    });

    return true;
}

@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
    response.setStatus(HttpStatus.OK_200);

    // Utility method to write UTF-8 string content.
    // When this write completes, the Handler callback is completed.
    Content.Sink.write(response, true, "Hello World", callback);

    return true;
}

@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
    response.setStatus(HttpStatus.OK_200);

    // Utility method to echo the content from the request to the response.
    // When the echo completes, the Handler callback is completed.
    Content.copy(request, response, callback);

    return true;
}

@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
    response.setStatus(HttpStatus.OK_200);

    // The trailers must be set on the response before the first write.
    HttpFields.Mutable trailers = HttpFields.build();
    response.setTrailersSupplier(trailers);

    // Explicit first write that writes the response status code, headers and content.
    // The trailers have not been written yet; they will be written with the last write.
    ByteBuffer content = UTF_8.encode("Hello World");
    Callback.Completable completable = new Callback.Completable();
    response.write(false, content, completable);

    completable.whenComplete((ignored, failure) ->
    {
        if (failure == null)
        {
            // Update the trailers
            trailers.put("Content-Checksum", 0xCAFE);

            // Explicit last write to write the trailers
            // and complete the Handler callback.
            response.write(true, null, callback);
        }
        else
        {
            // Implicitly respond with status code 500.
            callback.failed(failure);
        }
    });

    return true;
}

APIs Changes

HttpClient

The Jetty 11 Request.onResponseContentDemanded(Response.DemandedContentListener) API has been replaced by Request.onResponseContentSource(Response.ContentSourceListener) in Jetty 12.

However, also look at Request.onResponseContentAsync(Response.AsyncContentListener) and Request.onResponseContent(Response.ContentListener) for simpler usages.

The Jetty 11 model was a "demand+push" model: the application was demanding content; when the content was available, the implementation was pushing content to the application by calling DemandedContentListener.onContent(Response, LongConsumer, ByteBuffer, Callback) for every content chunk.

The Jetty 12 model is a "demand+pull" model: when the content is available, the implementation calls once Response.ContentSourceListener.onContentSource(Content.Source); the application can then pull the content chunks from the Content.Source.

For more information about the new model, see this section.

WebSocket

The Jetty WebSocket APIs have been vastly simplified, and brought in line with the style of other APIs.

The Jetty 12 WebSocket APIs are now fully asynchronous, so the Jetty 11 SuspendToken class has been removed in favor of an explicit (or automatic) demand mechanism in Jetty 12 (for more information, refer to this section).

The various Jetty 11 WebSocket*Listener interfaces have been replaced by a single interface in Jetty 12, Session.Listener.AutoDemanding (for more information, refer to this section).

The Jetty 11 RemoteEndpoint APIs have been merged into the Session APIs in Jetty 12.

The Jetty 11 WriteCallback class has been renamed to just Callback in Jetty 12, because it is now also used when receiving binary data. Note that this Callback interface is a different interface from the org.eclipse.jetty.util.Callback interface, which cannot be used in the Jetty WebSocket APIs due to class loader visibility issues.

On the server-side, the Jetty WebSocket APIs have been made independent of the Servlet APIs.

Jetty 11 JettyWebSocketServerContainer has been replaced by ServerWebSocketContainer in Jetty 12, with similar APIs (for more information, refer to this section).

On the client-side the WebSocketClient APIs are practically unchanged, as most of the changes come from the HttpClient changes described above.