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

Server-Side Web Application APIs Changes

Jetty 12 introduced redesigned server-side APIs for web applications. In Jetty 11, these APIs were based on a mix of Jakarta Servlet APIs and Jetty Handler APIs, while in Jetty 12 they are solely based on Jetty Handler APIs.

In Jetty 12 you can now write web applications independently of the Servlet APIs, so you can migrate Jakarta Servlets to Jetty Handlers as explained in this section.

If you were already using the Jetty 11 Handler APIs, you can migrate them to the Jetty 12 Handler APIs as explained in this section.

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 true;
    }
}

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.
    Content.Source.asString(request, UTF_8, new Promise<>()
    {
        @Override
        public void succeeded(String result)
        {
            // Process the request content here.

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

        @Override
        public void failed(Throwable failure)
        {
            // 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.
    Content.Source.asByteBuffer(request, new Promise<>()
    {
        @Override
        public void succeeded(ByteBuffer result)
        {
            // Process the request content here.

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

        @Override
        public void failed(Throwable failure)
        {
            // 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
{
    // When the read is complete, complete the Handler callback.
    Promise.Task<Void> reader = new Promise.Task<>(callback::succeeded, callback::failed)
    {
        @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();
                    failed(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())
                {
                    succeeded(null);
                    return;
                }

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

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

    return true;
}

@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
    FormFields.onFields(request, new Promise.Invocable<>()
    {
        @Override
        public void succeeded(Fields fields)
        {
            // Process the form fields here.

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

        @Override
        public void failed(Throwable failure)
        {
            // Implicitly respond with status code 500.
            callback.failed(failure);
        }
    });

    return true;
}

@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
    String contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE);

    MultiPartConfig multiPartConfig = new MultiPartConfig.Builder()
        // The directory where the parts content are saved.
        .location(Path.of("/tmp"))
        .build();

    MultiPartFormData.onParts(request, request, contentType, multiPartConfig, new Promise.Invocable<>()
    {
        @Override
        public void succeeded(MultiPartFormData.Parts parts)
        {
            // Process the parts here.

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

        @Override
        public void failed(Throwable failure)
        {
            // Implicitly respond with status code 500.
            callback.failed(failure);
        }
    });

    return true;
}

Refer also to the Content.Source APIs detailed in this section.

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 = Response.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;
}

Refer also to the Content.Sink APIs detailed in this section.

APIs Changes

Handler

The server-side Handler class, and the APIs to use for request/response processing, have been redesigned in Jetty 12.

The Jetty 11 Handler method:

Handler.handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)

has been changed in Jetty 12 to:

Handler.handle(Request request, Response response, Callback callback)

The Jetty 11 target parameter has been removed, and in Jetty 12 it has been replaced by the information present in Request.getHttpURI().

In Jetty 11, Handlers would mark the fact that they handled the request, and therefore are producing a response, by calling Request.setHandled(true). In Jetty 12, this is performed by returning true from the Handler.handle(...) method, which also requires that the Callback parameter must be completed, either by succeeding it or failing it.

In Jetty 11, the Handler.handle(...) method has a blocking semantic, while in Jetty 12 the Handler.handle(...) method has an asynchronous semantic, thanks to the Callback parameter. This means that you can return from the Handler.handle(...) method before the response has been sent, similarly to what you can do with the Servlet APIs when you call HttpServletRequest.startAsync(). Similarly, in Jetty 11 after a call to startAsync() you must call AsyncContext.complete(), while in Jetty 12 you must complete the Callback parameter, either by succeeding it or failing it.

In Jetty 11, AbstractHandler provides a utility class to implement Handler. In Jetty 12, use Handler.Abstract.

In Jetty 11, the APIs to deal with request or response HTTP headers are based on either Jetty’s HttpFields, or the Servlet APIs. In Jetty 12, the HTTP headers API are only based on HttpFields. Please refer to the HttpFields javadocs for details.

In Jetty 11, the request content is accessed via Request.getInputStream() or HttpServletRequest.getInputStream(). In Jetty 12, the Request object itself is-a Content.Source that can be read as explained in this section. In Jetty 12, you can use Content.Source.asInputStream(request) to obtain an InputStream and minimize the changes to your code, but remember that InputStream only provides blocking APIs, while Content.Source provides non-blocking APIs.

In Jetty 11, the response content is accessed via Response.getOutputStream() or HttpServletResponse.getOutputStream(). In Jetty 12, the Response object itself is-a Content.Sink that can be written as explained in this section. In Jetty 12, you can use Content.Sink.asOutputStream(response) to obtain an OutputStream and minimize the changes to your code, but remember that OutputStream only provides blocking APIs, while Content.Sink provides non-blocking APIs.

HandlerCollection and HandlerList

Jetty 11’s org.eclipse.jetty.server.handler.HandlerCollection and org.eclipse.jetty.server.handler.HandlerList have been replaced by Jetty 12’s org.eclipse.jetty.server.handler.Handler.Sequence.

Note that class org.eclipse.jetty.server.handler.ContextHandlerCollection has been retained (with the updates introduced by Handler as described here), and it is typically a better choice: where Handler.Sequence just iterates all its children Handlers until one return true from the handle(Request, Response, Callback) method, ContextHandlerCollection more efficiently selects the child ContextHandler based on context path and virtual hosts.

RequestLogHandler

Jetty 11’s org.eclipse.jetty.server.handler.RequestLogHandler has been removed with no replacement.

In Jetty 12, request logging is achieved by calling Server.setRequestLog(RequestLog requestLog), as described in this section.

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.

Jetty 12 introduced the concept of low-level transport for high-level protocols, described in 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.