Home | Send Feedback | Share on Bluesky |

Methanol - A convenient extension of the Java HTTP client

Published: 2. August 2025  •  java

Since Java 11, the JDK has included an HTTP client in the java.net.http package. This client is feature complete, and you can do everything you need with it. However, it lacks some convenience features that sometimes need a lot of boilerplate code to implement. Methanol is a library that fills these gaps, providing a set of extensions to the standard HTTP client.

Methanol is not a new HTTP client, it sits on top of the standard Java HTTP client and extends it with additional features, like caching, response decompression, and more. It is designed to be lightweight and easy to use. The library is split into multiple modules, allowing you to include only the features you need in your project.

The source code of Methanol is available on GitHub at mizosoft/methanol and is licensed under the MIT License.

Setup

To get started with Methanol, you add this dependency to your project.

    <dependency>
      <groupId>com.github.mizosoft.methanol</groupId>
      <artifactId>methanol</artifactId>
      <version>1.8.3</version>
    </dependency>

pom.xml

This is the core library, which has no runtime dependencies.


In your application you instantiate a Methanol client, which is a subclass of the standard java.net.http.HttpClient. You can configure it with various options. The builder provides methods to configure all the options that the standard HttpClient supports, like authenticators, timeouts, cookie handlers, executors, redirect policies, and more.

Methanol adds some convenience features like setting a default User-Agent, base URI, and default headers. Each time an application sends a request, Methanol will automatically apply these settings to the request. This way you can avoid repeating the same configuration for every request.

In the builder you can also configure interceptors and caching, which are not available in the standard HttpClient.

    HttpCache cache = HttpCache.newBuilder()
        .cacheOnDisk(Path.of(".cache"), (long) (100 * 1024 * 1024)).build();

    Methanol.Builder builder = Methanol.newBuilder().userAgent("MyCustomUserAgent/1.0")
        .baseUri("https://api.github.com").defaultHeader("Accept", "application/json")
        .requestTimeout(Duration.ofSeconds(20)).headersTimeout(Duration.ofSeconds(5))
        .connectTimeout(Duration.ofSeconds(10)).readTimeout(Duration.ofSeconds(5))
        .cache(cache).interceptor(new LoggingInterceptor());

    Methanol client = builder.build();

Demo2.java

You can also build a Methanol instance from an existing java.net.http.HttpClient instance.

var jdkHttpClient = HttpClient.newHttpClient();
var methanolClient = Methanol.newBuilder(jdkHttpClient).build();

Adapters

Another new feature of Methanol is adapters. An adapter is code that converts request bodies and response bodies to and from a specific format. Methanol provides several prebuilt adapters for common formats, like JSON, XML, Protocol Buffers, and more. You can also create your own adapters if needed.

This example configures the prebuilt Jackson adapter to handle JSON requests and responses.

  private static final Methanol client = Methanol.newBuilder()
      .baseUri("http://localhost:8080")
      .adapterCodec(AdapterCodec.newBuilder().basic()
          .encoder(JacksonAdapterFactory.createJsonEncoder(objectMapper))
          .decoder(JacksonAdapterFactory.createJsonDecoder(objectMapper)).build())
      .cookieHandler(new java.net.CookieManager()).connectTimeout(Duration.ofSeconds(10))
      .readTimeout(Duration.ofSeconds(20)).build();

Demo.java

When configuring adapters, you can specify what mime type is handled by what adapter. This allows you to use different adapters for different content types. For example, you can use a JSON adapter for application/json and an XML adapter for application/xml.

When receiving a response, Methanol will automatically use the appropriate adapter based on the Content-Type response header. For sending requests, you need to specify the content type, so Methanol can select the correct adapter.

The prebuilt adapters are not part of the core library, you need to add the corresponding dependency to your project. Here are the available adapters:

The example above uses the Jackson adapter.

    <dependency>
      <groupId>com.github.mizosoft.methanol</groupId>
      <artifactId>methanol-jackson</artifactId>
      <version>1.8.3</version>
    </dependency>

pom.xml

Check out the Methanol documentation for more details on how to use adapters and how to implement your own adapters.

Sending Requests

To send requests with Methanol, you use the send method. You can create a request using the standard HttpRequest builder or use Methanol's MutableRequest, a class that extends the standard HttpRequest and provides additional features like setting tags, relative URIs, and arbitrary objects as request bodies.

When creating a request with a URI that does not have a host or scheme, Methanol will automatically resolve it against the base URI configured in the Methanol client. Methanol also applies all the default headers and settings configured in the client to the request. You can still add or override headers in the request itself.

    MutableRequest request = MutableRequest.GET("/api/data").header("X-Request-ID",
        "12345");
    HttpResponse<String> response = client.send(request, BodyHandlers.ofString());

Demo.java

As the name suggests, MutableRequest is mutable. When you pass it to other parts of the programs and want to make sure that it is not modified, you can convert it to an immutable request using the toImmutableRequest() method.


The second argument of the send method is usually a javax.net.http.HttpResponse.BodyHandler, which is used to handle the response body. Methanol adds a convenience variant of the send method that takes an arbitrary object as argument. This is a very convenient way to work with JSON or other formats, as you don't need to manually parse the response body. Methanol will automatically use the configured adapter to convert the response body to the specified type.

    HttpResponse<DataResponse> response = client.send(MutableRequest.GET("/api/data"),
        DataResponse.class);
    DataResponse data = response.body();

Demo.java

This example uses the Methanol client from the Adapters section, which has a Jackson adapter configured to handle JSON responses.


When sending POST requests, Methanol allows you to specify an arbitrary object as the request body. In that case, you also have to specify the content type (media type) of the request body, so Methanol can select the appropriate adapter to serialize the object, and to set the Content-Type header accordingly.

    ProcessRequest requestBody = new ProcessRequest("John", "Doe");
    HttpResponse<Map> response = client.send(
        MutableRequest.POST("/api/process", requestBody, MediaType.APPLICATION_JSON),
        Map.class);

    Map<String, Object> result = response.body();

Demo.java

Decompression

In the HTTP world, decompression works by allowing a client and server to agree on a compression algorithm (like Gzip or Brotli) to reduce the size of data transferred. The server compresses the response body before sending it, and the client, upon receiving the compressed data, decompresses it before processing. This significantly reduces bandwidth.

To agree on the compression method, the client includes an Accept-Encoding header in its request, indicating the compression methods it supports. The server can then choose to either ignore this header and send the body uncompressed or respond with a compressed body using one of the methods specified by the client. The server includes the Content-Encoding header in its response to indicate the compression method used. Based on this header, the client knows how to decompress the response body.

ServerClientServerClientServer compresses dataClient decompresses dataHTTP Request (Accept-Encoding: gzip, deflate)HTTP Response (Content-Encoding: gzip)

The standard Java HTTP client does not automatically handle decompression of response bodies. You have to manually add the Accept-Encoding header to your requests and handle the decompression of the response body yourself.

Methanol, on the other hand, automatically handles decompression for you. It adds the Accept-Encoding header to your requests and decompresses the response body based on the Content-Encoding header in the response.

Methanol out of the box supports gzip and deflate compression methods. Methanol automatically adds the Accept-Encoding: gzip, deflate header to your requests.

You can also configure Methanol to support Brotli compression by adding the methanol-brotli dependency to your project.

        <dependency>
            <groupId>com.github.mizosoft.methanol</groupId>
            <artifactId>methanol-brotli</artifactId>
            <version>1.8.3</version>
        </dependency>

pom.xml

With this dependency on the classpath, Methanol adds the Accept-Encoding: gzip, deflate, br header to the requests. There is no additional configuration needed. Note that this extension is only supported on Windows and Linux because it uses a native Brotli library through JNI. Read more about it in the documentation.


The decompression feature is enabled by default, you can disable it with the autoAcceptEncoding method in the builder.

    Methanol client = Methanol.newBuilder().autoAcceptEncoding(false).build();

Demo2.java

Multipart and Forms

Implementing multipart uploads with the standard Java HTTP client requires a lot of boilerplate code. Methanol provides a convenient way to create multipart requests with the MultipartBodyPublisher class. This class allows you to easily add text, form and file parts to requests.

    Path tempFile = Files.createTempFile("upload", ".txt");
    Files.writeString(tempFile, "Test file content");

    MultipartBodyPublisher multipartBody = MultipartBodyPublisher.newBuilder()
        .filePart("file", tempFile).textPart("description", "Demo upload")
        .textPart("tags", "demo").textPart("tags", "test").build();

    HttpResponse<String> response = client
        .send(MutableRequest.POST("/api/upload", multipartBody), BodyHandlers.ofString());

Demo.java


The other common way to post data to a server is using form data. In this case, the parameters are sent as key-value pairs concatenated with & and encoded as application/x-www-form-urlencoded. Methanol provides the FormBodyPublisher class that allows you to easily create form data requests.

    FormBodyPublisher.Builder builder = FormBodyPublisher.newBuilder();
    builder.query("name", "Jane Doe");
    builder.query("email", "jane@example.com").build();

    HttpResponse<String> response = client
        .send(MutableRequest.POST("/api/form", builder.build()), BodyHandlers.ofString());

Demo.java

Caching

Caching is a feature to reduce the number of requests sent to a server and improve performance by storing responses locally. The standard Java HTTP client does not have support for caching. Methanol fills this gap with the caching feature. In the core library you find two cache implementations: memory and disk.

Memory

    HttpCache cache = HttpCache.newBuilder().cacheOnMemory((long) (100 * 1024 * 1024))
        .build();
    Methanol client = Methanol.newBuilder().cache(cache).build();

Demo2.java


Disk

    HttpCache cache = HttpCache.newBuilder()
        .cacheOnDisk(Path.of(".cache"), (long) (100 * 1024 * 1024)).build();
    Methanol client = Methanol.newBuilder().cache(cache).build();

Demo2.java

A cache is a transparent layer between the client and the server. Besides configuring the cache in the client, you don't have to configure anything special when sending a request.

After configuring the cache in the client, Methanol will automatically use it. Important to note that Methanol only caches responses of GET requests, and it never stores partial responses (206). See this page for more information: Limitations.

Caching is quite a complex topic in the HTTP world, and Methanol tries to follow the HTTP caching specifications as closely as possible. It supports the standard HTTP caching headers like Cache-Control, Expires, ETag, and Last-Modified. The cache will automatically handle these headers and use them to determine whether a response can be served from the cache or if a new request needs to be sent to the server.

You can override default cache behavior on a per-request basis, either with the cacheControl method of the MutableRequest class or by setting the Cache-Control header directly.

    var cacheControl = CacheControl.newBuilder().maxAge(Duration.ofMinutes(30))
        .staleIfError(Duration.ofSeconds(60)).build();
    MutableRequest request1 = MutableRequest.GET("/api/data").cacheControl(cacheControl);

    MutableRequest request2 = MutableRequest.GET("/api/data").header("Cache-Control",
        "max-age=1800, stale-if-error=60");

Demo.java


In addition to the two built-in cache implementations, Methanol allows you to implement your own cache storage by implementing the com.github.mizosoft.methanol.StorageExtension interface.

Methanol provides one library that implements this interface, which is the Redis storage extension. As the name suggests, it stores the cache in a Redis database.

    <dependency>
      <groupId>com.github.mizosoft.methanol</groupId>
      <artifactId>methanol-redis</artifactId>
      <version>1.8.3</version>
    </dependency>    

pom.xml

    RedisURI redisUri = RedisURI.create("redis://localhost:6379");
    HttpCache cache = HttpCache.newBuilder()
        .cacheOn(RedisStorageExtension.newBuilder().standalone(redisUri).build()).build();
    Methanol client = Methanol.newBuilder().cache(cache).build();

Demo2.java

Check out the Methanol documentation for more indepth information on caching.

More Features

Methanol provides more features I did not cover in this article. Three features I find particularly useful I want to briefly mention here. In the official documentation you can find more details.

Interceptors allow you to inspect, mutate, retry and short-circuit HTTP calls, for example, to log them or modify them before they are sent.

Progress tracking allows you to track the progress of requests and responses, for example, to show a progress bar in the UI or to log the progress. Methanol provides a ProgressTracker class for this purpose.

Streaming requests allow you to send large requests without loading the entire request body into memory. This is useful for uploading large files or sending large amounts of data. Methanol provides publishers for asynchronously streaming the request body into an OutputStream or a WritableByteChannel in the MoreBodyPublishers class.

Conclusion

Methanol is a powerful and flexible library that extends the standard Java HTTP client with additional features. It provides a convenient way to work with HTTP requests and responses, making it easier to work with. The library is lightweight, modular, and easy to use, and it integrates seamlessly with the standard Java HTTP client.