AWS Developer Tools Blog

Smithy Java client framework is now generally available

Smithy Java client code generation is now generally available. You can use it to build type-safe, protocol-agnostic Java clients directly from Smithy models. With Smithy Java, serialization, protocol handling, and request/response lifecycles are all generated automatically from your model. This removes the need to write or maintain any of this code by hand.

In this post, you will learn what Smithy Java client generation is, how it works, what makes it different, and how you can use it. Modern service development is built on strong contracts and automation. Smithy provides a model-driven approach to defining services and generating code from those definitions. It produces clients, services, and documentation from a single source of truth that stays aligned with your API as it evolves. Smithy Java client code generation enforces protocol correctness and removes serialization boilerplate, so you can focus on building features instead of hand-writing requests and responses.

How it works

At a high level, Smithy Java client code generation transforms Smithy models into strongly typed Java clients.

Model-driven development

At the core of the workflow is modeling services using Smithy. You define services, operations, and data shapes in a declarative format that captures API structure, constraints, and protocol bindings. These models act as the canonical definition of the API surface. For example:

namespace com.example

use aws.api#service
use smithy.protocols#rpcv2Cbor

@title("Coffee Shop Service")
@rpcv2Cbor
@service(sdkId: "CoffeeShop")
service CoffeeShop {
    version: "2024-08-23"
    operations: [
        GetMenu
    ]
}

@readonly
operation GetMenu {
    output := {
        items: CoffeeItems
    }
} 
...

Smithy Java consumes the models and produces Java client code. The generated output includes typed operations, serializers, deserializers, and protocol handling.

For more information about writing Smithy models, see Smithy’s quick start documentation.

Generated clients

The generated clients support a range of features that are typical for client-service communication, including request/response handling, serialization, protocol negotiation, retries, error mapping, and custom interceptors. You only need to define them in the model, and Smithy Java writes the code for you.

The following is an example of a generated Java client:

var client = CoffeeShopClient.builder()
    .endpointProvider(EndpointResolver.staticEndpoint("http://localhost:8888"))
    .build();

var menu = client.getMenu();

You can regenerate clients after API changes to the model, keeping them up to date without writing any manual code.

For more information about how to start generating Java clients from Smithy models, see our quick start guide.

Key capabilities

Protocol flexibility

Smithy Java generated clients are protocol-agnostic. The framework includes built-in support for HTTP transport, AWS protocols (including AWS JSON 1.0/1.1, restJson1, restXml and Query), and Smithy RPCv2 CBOR. You can swap protocols at runtime without rebuilding the client, enabling gradual protocol migrations and multi-protocol support with no code changes.

Dynamic client

Not every use case requires code generation at build time. Smithy Java includes a dynamic client that loads Smithy models at runtime and can interact with any service API without a codegen step. This is particularly useful for building tools, service aggregators, or systems that must interact with unknown services at build time, all while keeping the deployment footprint small.

The following is an example of calling the Coffee Shop service using the DynamicClient :

var model = Model.assembler().addImport("model.smithy").assemble().unwrap();
var serviceId = ShapeId.from("com.example#CoffeeShop");
var client = DynamicClient.builder().model(model).serviceId(serviceId).build();
var result = client.call("GetMenu");

Shape code generation independent of services

Smithy Java can generate type-safe Java classes from Smithy shapes without any service context. This extends Smithy’s model-first approach beyond service calls into the data and logic layers of your system, enabling code reuse and consistency across projects that share common types.

Built on Java virtual threads

Smithy Java is built from the ground up around Java 21’s virtual threads. Instead of exposing complex async APIs with callbacks or reactive streams, it provides a blocking-style interface that is straightforward to read, write, and debug, without sacrificing performance. Users can concentrate on their business logic while letting Smithy Java and the JVM handle task scheduling, synchronization, and structured error handling.

The following example demonstrates using Amazon Transcribe with Smithy’s Java event streams blocking API. To send an event, Smithy clients use a EventStreamWriter<T> with a write(T event) method, and to receive an event the client uses EventStreamReader with a T read() method. For example:

// Create an Amazon Transcribe client
var client = TranscribeClient.builder().build();
var audioStream = EventStream.<AudioStream>newWriter();

// Create a stream transcription request
var request = StartStreamTranscriptionInput.builder().audioStream(audioStream).build();

// Create a VT to send the audio that we want to transcribe
Thread.startVirtualThread(() -> {
    try (var audioStreamWriter = audioStream.asWriter()) {
        for (var chunk : iterableAudioChunks()) {
            var event = AudioEvent.builder().audioChunk(chunk).build()
            audioStreamWriter.write(AudioStream.builder().audioEvent(event).build());
        }
    }
});

// Send the request to Amazon Transcribe
var response = client.startStreamTranscription(request);

// Create a VT to read the transcription from the audio.
Thread.startVirtualThread(() -> {
    // The reader
    try (var results = response.getTranscriptResultStream().asReader()) {
        // The reader implements Iterable
        for (var event : results) {
            switch (event) {
                case TranscriptResultStream.TranscriptEventMember transcript -> {
                    var transcriptText = getTranscript(transcript);
                    if (transcriptText != null) {
                        appendAudioTranscript(transcriptText);
                    }
                }
                default -> throw new IllegalStateException("Unexpected event " + event);
            }
        }
    }
});

Conclusion

In this post, I explained what Smithy Java client generation is and how it works. With this general availability release, Smithy Java’s public APIs are now stable; we commit to backwards compatibility, making it ready for use in production systems. To get started with Smithy Java client code generation, use our quick start guide and documentation. If you want to send us feedback, ask a question, or discuss, you can reach us through GitHub issues and GitHub discussions.


About the author

TAGS:
Manuel Sugawara

Manuel Sugawara

Manuel Sugawara is a Sr Software Development Engineer in the AWS SDKs and Tools organization where he works on Smithy.