CookbookStream in Java

Stream in Java

stream_options.include_usage is Preview (not available yet). Until it ships, leave it out; the code below simply prints no usage line.

Print a reply as it is generated with java.net.http and Jackson.

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.*;
import java.util.stream.Stream;
 
public class StreamDemo {
    public static void main(String[] args) throws Exception {
        ObjectMapper m = new ObjectMapper();
        String body = """
            {"model": "Amara S1.2", "stream": true, "stream_options": {"include_usage": true},
             "messages": [{"role": "user", "content": "Explain idempotency keys in three sentences."}]}
            """;
        HttpRequest req = HttpRequest.newBuilder(URI.create("https://app.syntic.ai/v1/chat/completions"))
            .header("Authorization", "Bearer " + System.getenv("SYNTIC_API_KEY"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body)).build();
 
        HttpResponse<Stream<String>> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofLines());
        if (res.statusCode() != 200) {
            throw new RuntimeException(res.statusCode() + ": " + String.join("", res.body().toList()));
        }
        for (String line : (Iterable<String>) res.body()::iterator) {
            if (!line.startsWith("data: ")) continue;
            String payload = line.substring(6);
            if (payload.equals("[DONE]")) break;
            JsonNode chunk = m.readTree(payload);
            if (chunk.has("error")) throw new RuntimeException("stream interrupted: " + chunk.path("error").path("message").asText());
            JsonNode choices = chunk.path("choices");
            if (choices.size() > 0) System.out.print(choices.get(0).path("delta").path("content").asText(""));
            if (chunk.hasNonNull("usage")) {
                System.out.printf("%n[%d in / %d out tokens]%n", chunk.path("usage").path("prompt_tokens").asInt(), chunk.path("usage").path("completion_tokens").asInt());
            }
        }
    }
}

BodyHandlers.ofLines() delivers lines as they arrive, so text prints while the model is still writing.

What the code handles

  • Lines that start with data: carry JSON chunks; other lines (blank lines, : keep-alive comments) are skipped.
  • data: [DONE] ends the stream.
  • A chunk with an error object means the stream broke after it started; the text so far may be incomplete, so retry the whole request.
  • The last chunk before [DONE] carries usage when you send stream_options.include_usage.

See Stream format for every field.

Was this page helpful?