One of the big refactorings in Jetty 9 is the complete rewrite of the HTTP client.
The reasons behind the rewrite are many:

  • We wrote the codebase several years ago; while we have actively maintained, it was starting to show its age.
  • The HTTP client guarded internal data structures from multithreaded access using the synchronized keyword, rather than using non-blocking data structures.
  • We exposed as main concept the HTTP exchange that, while representing correctly what an HTTP request/response cycle is,  did not match user expectations of a request and a response.
  • HTTP client did not have out of the box features such as authentication, redirect and cookie support.
  • Users somehow perceived the Jetty HTTP client as cumbersome to program.

The rewrite takes into account many community inputs, requires JDK 7 to take advantage of the latest programming features, and is forward-looking because the new API is JDK 8 Lambda-ready (that is, you can use Jetty 9’s HTTP client with JDK 7 without Lambda, but if you use it in JDK 8 you can use lambda expressions to specify callbacks; see examples below).

Programming with Jetty 9’s HTTP Client

The main class is named, as in Jetty 7 and Jetty 8, org.eclipse.jetty.client.HttpClient (although it is not backward compatible with the same class in Jetty 7 and Jetty 8).
You can think of an HttpClient instance as a browser instance.
Like a browser, it can make requests to different domains, it manages redirects, cookies and authentications, you can configure it with a proxy, and it provides you with the responses to the requests you make.
You need to configure an HttpClient instance and then start it:

HttpClient httpClient = new HttpClient();
// Configure HttpClient here

Simple GET requests require just  one line:

ContentResponse response = httpClient

Method HttpClient.GET(...) returns a Future<ContentResponse> that you can use to cancel the request or to impose a total timeout for the request/response conversation.
Class ContentResponse represents a response with content; the content is limited by default to 2 MiB, but you can configure it to be larger.
Simple POST requests also require just one line:

ContentResponse response = httpClient
        .param("p", "value")
        .get(5, TimeUnit.SECONDS);

Jetty 9’s HttpClient automatically follows redirects, so automatically handles the typical web pattern POST/Redirect/GET, and the response object contains the content of the response of the GET request. Following redirects is a feature that you can enable/disable on a per-request basis or globally.
File uploads also require one line, and make use of JDK 7’s java.nio.file classes:

ContentResponse response = httpClient
        .get(5, TimeUnit.SECONDS);

Asynchronous Programming

So far we have shown how to use HttpClient in a blocking style, that is the thread that issues the request blocks until the request/response conversation is complete. However, to unleash the full power of Jetty 9’s HttpClient you should look at its non-blocking (asynchronous) features.
Jetty 9’s HttpClient fully supports the asynchronous programming style. You can write a simple GET request in this way:

        .send(new Response.CompleteListener()
            public void onComplete(Result result)
                // Your logic here

Method send(Response.CompleteListener) returns void and does not block; the Listener provided as a parameter is notified when the request/response conversation is complete, and the Result parameter  allows you to access the response object.
You can write the same code using JDK 8’s lambda expressions:

        .send((result) -> { /* Your logic here */ });

HttpClient uses Listeners extensively to provide hooks for all possible request and response events, and with JDK 8’s lambda expressions they’re even more fun to use:

        // Add request hooks
        .onRequestQueued((request) -> { ... })
        .onRequestBegin((request) -> { ... })
        // More request hooks available
        // Add response hooks
        .onResponseBegin((response) -> { ... })
        .onResponseHeaders((response) -> { ... })
        .onResponseContent((response, buffer) -> { ... })
        // More response hooks available
        .send((result) -> { ... });

This makes Jetty 9’s HttpClient suitable for HTTP load testing because, for example, you can accurately time every step of the request/response conversation (thus knowing where the request/response time is really spent).

Content Handling

Jetty 9’s HTTP client provides a number of utility classes off the shelf to handle request content and response content.
You can provide request content as String, byte[], ByteBuffer, java.nio.file.Path, InputStream, and provide your own implementation of ContentProvider. Here’s an example that provides the request content using an InputStream:

        .content(new InputStreamContentProvider(
        .send((result) -> { ... });

HttpClient can handle Response content in different ways:
The most common is via blocking calls that return a ContentResponse, as shown above.
When using non-blocking calls, you can use a BufferingResponseListener in this way:

        // Buffer response content up to 8 MiB
        .send(new BufferingResponseListener(8 * 1024 * 1024)
            public void onComplete(Result result)
                if (!result.isFailed())
                    byte[] responseContent = getContent();
                    // Your logic here

To be efficient and avoid copying to a buffer the response content, you can use a Response.ContentListener, or a subclass:

ContentResponse response = httpClient
        .send(new Response.Listener.Empty()
            public void onContent(Response r, ByteBuffer b)
                // Your logic here

To stream the response content, you can use InputStreamResponseListener in this way:

InputStreamResponseListener listener =
        new InputStreamResponseListener();
// Wait for the response headers to arrive
Response response = listener.get(5, TimeUnit.SECONDS);
// Look at the response
if (response.getStatus() == 200)
    InputStream stream = listener.getInputStream();
    // Your logic here

Cookies Support

HttpClient stores and accesses HTTP cookies through a CookieStore:

Destination d = httpClient
        .getDestination("http", "", 80);
CookieStore c = httpClient.getCookieStore();
List cookies = c.findCookies(d, "/path");

You can add cookies that you want to send along with your requests (if they match the domain and path and are not expired), and responses containing cookies automatically populate the cookie store, so that you can query it to find the cookies you are expecting with your responses.

Authentication Support

HttpClient suports HTTP Basic and Digest authentications, and other mechanisms are pluggable.
You can configure authentication credentials in the HTTP client instance as follows:

String uri = "";
String realm = "MyRealm";
String u = "username";
String p = "password";
// Add authentication credentials
AuthenticationStore a = httpClient.getAuthenticationStore();
    new BasicAuthentication(uri, realm, u, p));
ContentResponse response = httpClient
        .get(5, TimeUnit.SECONDS);

HttpClient tests authentication credentials against the challenge(s) the server issues, and if they match it automatically sends the right authentication headers to the server for authentication. If the authentication is successful, it caches the result and reuses it for subsequent requests for the same domain and matching URIs.

Proxy Support

You can also configure HttpClient  with a proxy:

    new ProxyConfiguration("proxyHost", proxyPort);
ContentResponse response = httpClient
        .get(5, TimeUnit.SECONDS);

Configured in this way, HttpClient makes requests to the proxy (for plain-text HTTP requests) or establishes a tunnel via HTTP CONNECT (for encrypted HTTPS requests).


The new Jetty 9  HTTP client is easier to use, has more features and it’s faster and better than Jetty 7’s or Jetty 8’s.
The Jetty project continues to lead the way when it’s about the Web: years ago with Jetty Continuations, then with Jetty WebSocket, recently with Jetty SPDY and now with the first complete, ready to use, JDK 8’s Lambda -ready HTTP client.
Go get it while it’s hot !
Maven coordinates:


Direct Downloads:
Main jar: jetty-client.jar
Dependencies: jetty-http.jar, jetty-io.jar, jetty-util.jar


Tomas Theunissen · 22/11/2012 at 20:34

This is one of the reasons why I love Jetty so much, always leading the way with new features! I can’t wait until I can use the async features with lambda’s. Great job guys!

Lars Vogel · 23/11/2012 at 14:22

Looks very concise. Did you every try to use your HttpClient implementation in Android?

Michał · 28/01/2013 at 18:52

The new Jetty Client does not handle exceptions very well. Each exception is from different family. Sometimes onFailure is not executed 🙁
Unknown host
— CUT —
2013-01-28 19:18:38,117 [pool-1-thread-1] WARN – send() uri=http://localhostx, e=java.nio.channels.UnresolvedAddressException
— CUT —
Connection refused:
— CUT —
2013-01-28 19:19:33,071 [HttpClient@2078559224-12] INFO – onFailure response=HttpResponse[null 0 null], Connection refused
2013-01-28 19:19:33,072 [HttpClient@2078559224-12] INFO – onComplete response.getStatus=0, request.getUri=http://localhost:23
— CUT —
If you don’t specify port, invalid port number is put in onComplete() result.getRequest.getUri():
— CUT —
2013-01-28 19:34:40,981 [pool-1-thread-1] INFO – send() uri=http://localhost/, duration=76080
2013-01-28 19:34:40,985 [HttpClient@2078559224-12] INFO – onFailure response=HttpResponse[null 0 null], Połączenie odrzucone
2013-01-28 19:34:40,986 [HttpClient@2078559224-12] INFO – onComplete response.getStatus=0, request.getUri=http://localhost:-1/
— CUT —
Protocol violation (I try connecting to SSH):
— CUT —
2013-01-28 19:24:39,960 [HttpClient@2078559224-13] INFO – onFailure response=HttpResponse[null 400 Unknown Version], failure=org.eclipse.jetty.client.HttpResponseException: HTTP protocol violation: bad response
2013-01-28 19:24:39,961 [HttpClient@2078559224-13] INFO – onComplete response.getStatus=400, request.getUri=http://localhost:22/
— CUT —
But sometimes onFailure isn’t executed:
— CUT —
2013-01-28 19:25:55,602 [pool-1-thread-1] INFO – send() uri=http://localhost:22/, duration=69379
2013-01-28 19:25:55,695 [HttpClient@2078559224-12] INFO – onComplete response.getStatus=0, request.getUri=http://localhost:22/
— CUT —
Early EOF:
— CUT —
2013-01-28 19:27:53,447 [pool-1-thread-1] INFO – send() uri=http://localhost:9010/, duration=17209
2013-01-28 19:27:53,502 [HttpClient@2078559224-13] INFO – onFailure response=HttpResponse[null 0 null],
2013-01-28 19:27:53,503 [HttpClient@2078559224-13] INFO – onComplete response.getStatus=0, request.getUri=http://localhost:9010/
— CUT —
— CUT —
2013-01-28 19:29:22,645 [pool-1-thread-1] INFO – send() uri=http://localhost:80/, duration=17055
2013-01-28 19:29:37,652 [HttpClient@1208474529-12] INFO – onFailure response=HttpResponse[null 0 null],
2013-01-28 19:29:37,654 [HttpClient@1208474529-12] INFO – onComplete response.getStatus=0, request.getUri=http://localhost:80/
— CUT —
— CUT —
2013-01-28 19:39:50,948 [pool-1-thread-1] WARN – send() uri=http://x3.localhost/, e=java.nio.channels.UnresolvedAddressException, duration=85627
— CUT —
If connections gets redirecteded and host could not be resolved, then exception is thrown in client thread pool 🙁
— CUT —
2013-01-28 19:48:56,037 [HttpClient@1384238640-12] INFO – Exception while notifying listener org.eclipse.jetty.client.RedirectProtocolHandler@2f6b007f
at org.eclipse.jetty.client.HttpClient.newConnection(
at org.eclipse.jetty.client.HttpDestination.newConnection(
at org.eclipse.jetty.client.HttpDestination.acquire(
at org.eclipse.jetty.client.HttpDestination.send(
at org.eclipse.jetty.client.HttpClient.send(
at org.eclipse.jetty.client.HttpRequest.send(
at org.eclipse.jetty.client.RedirectProtocolHandler.redirect(
at org.eclipse.jetty.client.RedirectProtocolHandler.onComplete(
at org.eclipse.jetty.client.ResponseNotifier.notifyComplete(
at org.eclipse.jetty.client.ResponseNotifier.notifyComplete(
at org.eclipse.jetty.client.HttpReceiver.success(
at org.eclipse.jetty.client.HttpReceiver.messageComplete(
at org.eclipse.jetty.http.HttpParser.parseNext(
at org.eclipse.jetty.client.HttpReceiver.parse(
at org.eclipse.jetty.client.HttpReceiver.receive(
at org.eclipse.jetty.client.HttpConnection.receive(
at org.eclipse.jetty.client.HttpExchange.receive(
at org.eclipse.jetty.client.HttpConnection.onFillable(
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(
at org.eclipse.jetty.util.thread.QueuedThreadPool$
— CUT —
It remains to say that the send () uses a blocking DNS client :/

    simon · 28/01/2013 at 22:34

    Michal, thanks for the report.
    #1 is tracked by
    #2 is expected behavior.
    #3 is tracked by
    #4 and #5 work fine for me. Do you have additional information or a reproducible test case ?
    #6 is expected behavior.
    #7 is expected behavior.
    #8 is the same bug as #1.
    #9 is the same bug as #1.
    About the use of blocking DNS client, I am not sure what you mean exactly. DNS lookup is performed by the JVM and I am not sure we can control it.
    If you don’t want to wait too much for DNS lookups, you can specify a connect timeout via HttpClient.setConnectTimeout(long).
    Finally, I am not sure I understand your comment about exception handling. Bugs aside, asynchronous processing can only notify applications via callbacks, and does not seem right to have N callbacks for the N possible exceptions that may happen. Hence, there is only one callback for failures, and if you really want to handle exceptions, you have to perform ugly if ... instanceof ... else if ... instanceof ....
    I am open to any suggestion to improve failure handling, if you have ideas.
    Thanks !

Michał · 29/01/2013 at 10:12

I will check my milestone version to verify if is actual and create test for onFailure() problem.
Client is asynchronous at most the time, but DNS resolver isn’t (send will block until get response from DNS). My idea is to resolve DNS asynchronously in client. In my test, I try to send 500 requests to different domains. In this case, send() block horribly.
Maybe it would be good to gather all the exceptions and document the situations in which they occur.

Arul Dhesiaseelan · 06/02/2013 at 08:56

Hi Simon,
This is pretty cool, I love the new APIs.
Is there a way to write entity directly to OutputStream, instead of using built-in content providers?

    simon · 07/02/2013 at 07:42

    Arul, you can use DeferredContentProvider as explained in the documentation.
    We may provide a ContentProvider that uses OutputStream based on DeferredContentProvider, but there would be no relevant difference between the two.

Arul Dhesiaseelan · 08/02/2013 at 08:03

Thanks for the pointer. It is not exactly what I was looking for. In my case, I would like to delegate the content handling to my JAX-RS Client framework which is quite powerful and it could bind to various content type from the stream. It would be very useful if Jetty Client supported a ContentProvider that writes to OutputStream. Something like this would help :

public interface StreamingProvider extends ContentProvider {
void write(OutputStream out) throws IOException;

    simon · 08/02/2013 at 08:26

    Arul, sorry but I don’t understand how you would use this StreamingProvider, can you make an extended example ?

Arul Dhesiaseelan · 11/02/2013 at 09:39

Hi Simon,
I put together a Jetty based JAX-RS 2 Client backend here
The code that you may want to look at is here:
Look at line#304 where I construct an OutputStream and stick it to Jetty ContentProvider.
Let me know what you think.

Jo · 02/03/2013 at 19:28

I’m using 9.0.0.RC2 and though it’s excellent, have a couple of comments on it:
a) If you make an HTTP Request and there is an available Connection then the request is processed in the calling thread (HttpDestination line 173) whereas if a Connection needs to be created it’s processed on a new thread (as part of a Promise created at HttpDestination line 226). Is this the intended behavior?
b) I love the fluid style of the CLient.newRequest method. What’s not really obvious is that if you add listeners for the various events by passing an object that implements more than one of the callback interfaces then the methods of that object are liable to be called more than once, since they’re called irrespective of the event you set the object on. e.g. in the following the events will be triggered twice:

public static class Listener implements Response.SuccessListener, Response.BeginListener, ...

This is presumably intended behavior?
c) Handling of redirects is nice, but if you’re using the client to measure performance, surely the request and response events should be triggered for each separate request/response pair of the redirection chain? Or would you expect the caller to implement redirects themselves if they are interested in that level of detail? Mind you, if you’re not interested in that level of detail you probably don’t care about most of the various events anyway.
d) I know this is picky, but I found it a bit confusing to start with: the name of the HttpField class accurately tracks standard usage (though perhaps should be HttpHeaderField), but the HttpHeader class should surely be HttpHeaderFieldName (or HttpFieldName). While I’m being picky, why does Client.setUserAgentField ask for a HttpField (and throw an exception if it’s the wrong one) and not just look for a String for the field value? And also for completeness, the request builder .header(String, String) method might more usefully take (HttpFieldName, String) or (HttpField), and for consistency might be called headerField()!
Thanks very much for an excellent implementation and thanks also for listening to these comments.

    simon · 04/03/2013 at 15:42

    a) Yes.
    b) If you register the same object for different events, then that object will be invoked for the events it is registered for. If you mean that one event (e.g. the begin event) is notified twice, then that’s not intended.
    c) If you are interested in knowing redirect performance, you can disable automatic redirect following, either at HttpClient level or at a request level.
    d) We reuse these classes on server. Perhaps you’re right about naming, but once you know them they’re short and simple.
    The user-agent field is an optimization: if you set it, it’s set once and not reconstructed for every request like we would have if we had only the value.
    About the overloaded methods, usually applications have strings and not instances of HttpField. But you convinced me on an overloaded version with HttpHeader.

      Jo · 04/03/2013 at 22:09

      Thanks for your reply.
      a) it seems a little counter-intuitive that a non-blocking client waits till it has sent the request before it becomes non-blocking … i.e. it’s non-blocking on the response, and also on the request but only if there’s no available connection already open.
      b) Yes. An object implementing more than one interface gets called duplicate times for the same event if it is registered for more than one event.
      c) Thanks. It doesn’t seem like it’s possible to find out if there has been one or more redirects, other than by comparing the requested address with the result address.
      Also, It looks like a redirected destination doesn’t work like a non-redirected destination. For example, if I request from here I get redirected to If I make requests to both of these in a loop, my direct request (to ends up being twice as fast even though the redirect is a “Moved Permanently”.
      As noted in a) above, once I’ve made the first request pair the dispatch of the second request of each pair blocks till the first is committed.
      d) Great! Could I possibly convince you also to support a headers(Headers) method, since all my requests always have all the same headers?
      thanks again for your reply.

        simon · 04/03/2013 at 22:42

        a) I don’t understand. There is no blocking code in HttpClient.
        b) Will double check this.
        c) We could have a cache of 301 URLs, but it’s not implemented. I don’t understand the comment on the loops nor the one on the fact that a second request would be blocked by a first ? There is no blocking code.
        d) Like b) and c) you have to file a request at

          Jo · 05/03/2013 at 07:18

          I didn’t explain myself too well. My application takes a set of URLs and loops over them to time responses.
          The first iteration of calls does not block, the second and subsequent iterations block because the destinations have been established and the call does not return till the request has been committed.
          Illustrated in the following using the Google examples above.

          07:11:59.148 [Thread-9] INFO org.eclipse.jetty.client.HttpClient - Started org.eclipse.jetty.client.HttpClient@14db5866
          07:11:59.148 [Thread-9] INFO com.example.Requester - Starting despatch of iteration 0
          07:11:59.152 [Thread-9] INFO com.example.Requester - Finished despatch of iteration 0 in 1.138 millis
          07:11:59.194 [HttpClient@349919334-23] INFO com.example.Requester -, Conversation 2 Request Begin: Increment 45.092 Cumulative 45.099 millis
          07:11:59.194 [HttpClient@349919334-23] INFO com.example.Requester -, Conversation 2 Request Commit: Increment 0.44 Cumulative 45.533 millis
          07:11:59.199 [HttpClient@349919334-24] INFO com.example.Requester -, Conversation 3 Request Begin: Increment 49.986 Cumulative 49.987 millis
          07:11:59.199 [HttpClient@349919334-24] INFO com.example.Requester -, Conversation 3 Request Commit: Increment 0.339 Cumulative 50.326 millis
          07:11:59.236 [HttpClient@349919334-22] INFO com.example.Requester -, Conversation 3 Response Begin: Increment 36.963 Cumulative 87.29 millis
          07:11:59.324 [HttpClient@349919334-21] INFO com.example.Requester -, Conversation 3 Response Complete (OK): Increment 87.449 Total 174.739 millis
          07:11:59.436 [HttpClient@349919334-23] INFO com.example.Requester -, Conversation 2 Response Begin: Increment 241.725 Cumulative 287.26 millis
          07:11:59.490 [HttpClient@349919334-22] INFO com.example.Requester -, Conversation 2 Response Complete (OK): Increment 53.909 Total 341.168 millis
          07:12:09.158 [Thread-9] INFO com.example.Requester - Starting despatch of iteration 1
          07:12:09.158 [Thread-9] INFO com.example.Requester -, Conversation 4 Request Begin: Increment 0.389 Cumulative 0.391 millis
          07:12:09.159 [Thread-9] INFO com.example.Requester -, Conversation 4 Request Commit: Increment 0.331 Cumulative 0.721 millis
          07:12:09.159 [Thread-9] INFO com.example.Requester -, Conversation 5 Request Begin: Increment 0.319 Cumulative 0.32 millis
          07:12:09.159 [Thread-9] INFO com.example.Requester -, Conversation 5 Request Commit: Increment 0.298 Cumulative 0.618 millis
          07:12:09.160 [Thread-9] INFO com.example.Requester - Finished despatch of iteration 1 in 1.819 millis
          07:12:09.211 [HttpClient@349919334-24] INFO com.example.Requester -, Conversation 5 Response Begin: Increment 51.102 Cumulative 51.72 millis
          07:12:09.358 [HttpClient@349919334-25] INFO com.example.Requester -, Conversation 5 Response Complete (OK): Increment 147.395 Total 199.116 millis
          07:12:09.359 [HttpClient@349919334-26] INFO com.example.Requester -, Conversation 4 Response Begin: Increment 200.579 Cumulative 201.3 millis
          07:12:09.397 [HttpClient@349919334-26] INFO com.example.Requester -, Conversation 4 Response Complete (OK): Increment 37.491 Total 238.791 millis

          Thanks again for your reply on this – I will file as you request.

Prashant · 14/03/2013 at 07:16

Thanks for the information, it was very helpful. In my application I am using Apache HTTP Client 4.x and now I am thinking to replace it with Jetty 9 HTTP Client because of the out of box listeners and easier to use. Although, I would like to know that whether there is any cache manager or any cache support in Jetty 9.0 HTTP Client ?. In my application I need to use a Cache Manager and Apache HTTP Client do provide a way to implement your own Caching.
Hope to get your reply, thanks in advance.
Prashant Singh

    simon · 14/03/2013 at 09:16

    There currently is no cache support in HttpClient.
    Please file a feature request at
    Thanks !

      Prashant · 14/03/2013 at 10:24

      Any ideas if I wanna implement it by my own way ? .. I would drop request anyways but would like to contribute to the community as well.

Prashant · 25/03/2013 at 15:21

How can I get the following times from the listeners:
1) Blocking time (in case request is waiting to get idle connection)
2) DNS Lookup time
3) Connecting to Remote Host Time
Actually I am trying to make a chart as like Firebug Net Panel to analyze the Response Time Breakdown. I saw there are many listeners but I didnt find the proper explanations for each listeners. Although most of them are pretty clear with the name but still I am having confusion to get the above mentioned time.
I would be thankful if you can explain me a little.

    simon · 25/03/2013 at 16:03

    1) Blocking time is request begin event minus request queued event (it may include DNS and connect times).
    2) Not currently exposed, although easy to expose.
    3) Same as 2).
    Note that DNS time and connect time are not related to a request.
    For example, a request may trigger a DNS lookup, but a subsequent request may be sent on an existing connection, so it does not perform DNS lookup nor connection attempt.
    Please file feature requests for 2) and 3) at explaining in details your use case.

Comments are closed.