HttpRequest.BodyPublishers.ofString()

Send a String as the request body for POST, PUT or PATCH, with the right Content-Type and a byte-accurate Content-Length.

Signature
static HttpRequest.BodyPublisher ofString(String s)
  • Added in Java Java 11

Parameters

ParameterTypeDescription
$sStringThe text to send as the request body.
$charsetoptionalCharset= StandardCharsets.UTF_8Only in the two-argument overload: the charset used to encode the string.

Return value

HttpRequest.BodyPublisherA body publisher that sends the string encoded as UTF-8, with a known content length in bytes. The overload ofString(String s, Charset charset) encodes with the given charset.

Errors

NullPointerException if s or charset is null.

HttpRequest.BodyPublishers.ofString(s) turns a String into a request body. Pass it to POST, PUT or method("PATCH", ...) on the request builder. The text is encoded as UTF-8, or with the charset you pass to ofString(s, charset).

HttpRequest request = HttpRequest.newBuilder(uri)
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(json))
        .build();

It does not set Content-Type

This is the mistake almost everyone makes once. The publisher supplies bytes and a length, nothing else. Without a Content-Type header the server does not know the body is JSON; a framework typically answers 415 Unsupported Media Type, or binds nothing and fails validation with a confusing message. Always add header("Content-Type", "application/json"), or the form type for form posts.

Content-Length is in bytes

The publisher knows its length up front, so the client sends a Content-Length header instead of chunked encoding. The length counts encoded bytes, not characters: "héllo" is five characters but six bytes in UTF-8. You never set this header yourself; the builder rejects it.

Charsets

UTF-8 is right for JSON, which RFC 8259 requires for documents exchanged between systems. Use the two-argument form only when the server needs something else, and then say so in the header too: text/plain; charset=ISO-8859-1. A body encoded one way and labelled another is how names with accents turn into question marks in someone's database.

Building the string

Producing JSON by concatenating strings is fine for fixed shapes, but any value from a user must be escaped, or a quote in a name breaks the document. In real services a JSON library such as Jackson writes the string. For HTML-style forms, join key=value pairs with &, encode each part with URLEncoder.encode(value, StandardCharsets.UTF_8), and send application/x-www-form-urlencoded.

Other publishers

noBody() for an empty POST, ofByteArray(bytes) for binary data, ofFile(path) to stream an upload from disk without loading it into memory, and ofInputStream(supplier) when the length is unknown.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Creating an order by POSTing a JSON document to another service and reading the Location header of the 201.
  • Replacing a user's profile with a PUT of the full JSON representation.
  • Requesting an OAuth access token by posting client_credentials as a form body.

Frequently asked questions

How do I POST JSON with Java HttpClient?

Build the request with header("Content-Type", "application/json") and POST(HttpRequest.BodyPublishers.ofString(json)), then call client.send(request, HttpResponse.BodyHandlers.ofString()). The JSON string itself usually comes from a library such as Jackson.

Does BodyPublishers.ofString set the Content-Type header?

No. It only provides the bytes and the content length. Set Content-Type yourself on the request builder.

Which charset does BodyPublishers.ofString use?

UTF-8. Use ofString(text, charset) for a different encoding, and name the same charset in the Content-Type header so the server decodes it correctly.

More examples

Forgetting Content-Type

The two requests carry identical bytes; only the header differs. The server here mimics what framework controllers do with an unlabelled body.

Example · java
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

// Writes a small text response; an empty body is sent with no body at all.
void reply(HttpExchange exchange, int status, String contentType, String body) throws IOException {
    byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
    if (contentType != null) {
        exchange.getResponseHeaders().set("Content-Type", contentType);
    }
    exchange.sendResponseHeaders(status, bytes.length == 0 ? -1 : bytes.length);
    try (OutputStream out = exchange.getResponseBody()) {
        out.write(bytes);
    }
}

void main() throws Exception {
    HttpServer server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
    server.createContext("/users", exchange -> {
        String type = exchange.getRequestHeaders().getFirst("Content-Type");
        exchange.getRequestBody().readAllBytes();
        if (type == null || !type.startsWith("application/json")) {
            reply(exchange, 415, "text/plain", "expected application/json, got " + type);
        } else {
            reply(exchange, 201, "text/plain", "created");
        }
    });
    server.start();
    URI uri = URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/users");
    String json = "{\"name\":\"Ada\"}";

    try (HttpClient client = HttpClient.newHttpClient()) {
        HttpRequest bare = HttpRequest.newBuilder(uri)
                .POST(HttpRequest.BodyPublishers.ofString(json))
                .build();
        HttpRequest labelled = HttpRequest.newBuilder(uri)
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(json))
                .build();
        for (HttpRequest request : List.of(bare, labelled)) {
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            IO.println(response.statusCode() + " " + response.body());
        }
    } finally {
        server.stop(0);
    }
}

// Output:
// 415 expected application/json, got null
// 201 created

Content-Length counts bytes, not characters

<code>contentLength()</code> is the value the client sends as <code>Content-Length</code>. The euro sign has no ISO-8859-1 form, so it is replaced by a single <code>?</code> byte: the length looks fine and the data is silently wrong.

Example · java
import java.net.http.HttpRequest;

void main() {
    for (String text : List.of("hello", "héllo", "€10", "")) {
        long utf8 = HttpRequest.BodyPublishers.ofString(text).contentLength();
        long latin1 = HttpRequest.BodyPublishers.ofString(text, StandardCharsets.ISO_8859_1).contentLength();
        IO.println(text.length() + " chars, UTF-8 " + utf8 + " bytes, ISO-8859-1 " + latin1 + " bytes");
    }
    IO.println("noBody: " + HttpRequest.BodyPublishers.noBody().contentLength());
}

// Output:
// 5 chars, UTF-8 5 bytes, ISO-8859-1 5 bytes
// 5 chars, UTF-8 6 bytes, ISO-8859-1 5 bytes
// 3 chars, UTF-8 5 bytes, ISO-8859-1 3 bytes
// 0 chars, UTF-8 0 bytes, ISO-8859-1 0 bytes
// noBody: 0

Posting an HTML-style form

Token endpoints and older APIs expect form bodies, not JSON. Each key and value is encoded separately, so the space and colons inside <code>scope</code> cannot be confused with the <code>&amp;</code> and <code>=</code> separators.

Example · java
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

// Writes a small text response; an empty body is sent with no body at all.
void reply(HttpExchange exchange, int status, String contentType, String body) throws IOException {
    byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
    if (contentType != null) {
        exchange.getResponseHeaders().set("Content-Type", contentType);
    }
    exchange.sendResponseHeaders(status, bytes.length == 0 ? -1 : bytes.length);
    try (OutputStream out = exchange.getResponseBody()) {
        out.write(bytes);
    }
}

String form(Map<String, String> fields) {
    return fields.entrySet().stream()
            .map(e -> URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8) + "="
                    + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
            .collect(Collectors.joining("&"));
}

void main() throws Exception {
    HttpServer server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
    server.createContext("/oauth/token", exchange -> {
        String body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
        reply(exchange, 200, "text/plain", exchange.getRequestHeaders().getFirst("Content-Type") + " | " + body);
    });
    server.start();
    URI uri = URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/oauth/token");

    Map<String, String> fields = new LinkedHashMap<>();
    fields.put("grant_type", "client_credentials");
    fields.put("scope", "orders:read orders:write");
    HttpRequest request = HttpRequest.newBuilder(uri)
            .header("Content-Type", "application/x-www-form-urlencoded")
            .POST(HttpRequest.BodyPublishers.ofString(form(fields)))
            .build();

    try (HttpClient client = HttpClient.newHttpClient()) {
        IO.println(client.send(request, HttpResponse.BodyHandlers.ofString()).body());
    } finally {
        server.stop(0);
    }
}

// Output:
// application/x-www-form-urlencoded | grant_type=client_credentials&scope=orders%3Aread+orders%3Awrite

Discussion

  • Be the first to comment on this lesson.