Class ClientBuilder

java.lang.Object
org.a2aproject.sdk.client.ClientBuilder

public class ClientBuilder extends Object
Builder for creating instances of Client to communicate with A2A agents.

ClientBuilder provides a fluent API for configuring and creating client instances that communicate with A2A agents. It handles transport negotiation, event consumer registration, and client configuration in a type-safe manner.

Key responsibilities:

  • Transport selection and negotiation between client and server capabilities
  • Event consumer registration for processing agent responses
  • Error handler configuration for streaming scenarios
  • Client behavior configuration (streaming, polling, preferences)

Transport Selection: The builder automatically negotiates the best transport protocol based on the agent's AgentCard and the client's configured transports. By default, the server's preferred transport (first in AgentCard.supportedInterfaces()) is used. This can be changed by setting ClientConfig.isUseClientPreference() to true.

Typical usage pattern:


 // 1. Get the agent card
 AgentCard card = A2A.getAgentCard("http://localhost:9999");

 // 2. Build client with transport and event consumer
 Client client = Client.builder(card)
     .withTransport(JSONRPCTransport.class, new JSONRPCTransportConfigBuilder())
     .addConsumer((event, agentCard) -> {
         if (event instanceof MessageEvent me) {
             System.out.println("Received: " + me.getMessage().parts());
         } else if (event instanceof TaskUpdateEvent tue) {
             System.out.println("Task status: " + tue.getTask().status().state());
         }
     })
     .build();

 // 3. Send messages
 client.sendMessage(A2A.toUserMessage("Hello agent!"));
 

Multiple transports: You can configure multiple transports for fallback:


 Client client = Client.builder(card)
     .withTransport(GrpcTransport.class, new GrpcTransportConfigBuilder()
         .channelFactory(ManagedChannelBuilder::forAddress))
     .withTransport(JSONRPCTransport.class, new JSONRPCTransportConfigBuilder())
     .clientConfig(new ClientConfig.Builder()
         .setUseClientPreference(true)  // Try client's preferred order
         .build())
     .build();
 

Error handling: For streaming scenarios, configure an error handler to process exceptions:


 Client client = Client.builder(card)
     .withTransport(JSONRPCTransport.class, new JSONRPCTransportConfigBuilder())
     .streamingErrorHandler(throwable -> {
         System.err.println("Stream error: " + throwable.getMessage());
     })
     .build();
 

Thread safety: ClientBuilder is not thread-safe and should only be used from a single thread during client construction. The resulting Client instance is thread-safe.

See Also:
  • Method Details

    • withTransport

      public <T extends ClientTransport> ClientBuilder withTransport(Class<T> clazz, ClientTransportConfigBuilder<? extends ClientTransportConfig<T>,?> configBuilder)
      Configure a transport protocol using a builder for type-safe configuration.

      Multiple transports can be configured to support fallback scenarios. The actual transport used is negotiated based on the agent's capabilities and the ClientConfig.

      Example:

      
       builder.withTransport(JSONRPCTransport.class,
           new JSONRPCTransportConfigBuilder()
               .httpClient(customHttpClient)
               .addInterceptor(loggingInterceptor));
       
      Type Parameters:
      T - the transport type
      Parameters:
      clazz - the transport class to configure
      configBuilder - the transport configuration builder
      Returns:
      this builder for method chaining
    • withTransport

      public <T extends ClientTransport> ClientBuilder withTransport(Class<T> clazz, ClientTransportConfig<T> config)
      Configure a transport protocol with a pre-built configuration.

      Multiple transports can be configured to support fallback scenarios. The actual transport used is negotiated based on the agent's capabilities and the ClientConfig.

      Example:

      
       JSONRPCTransportConfig config = new JSONRPCTransportConfig(myHttpClient);
       builder.withTransport(JSONRPCTransport.class, config);
       
      Type Parameters:
      T - the transport type
      Parameters:
      clazz - the transport class to configure
      config - the transport configuration
      Returns:
      this builder for method chaining
    • addConsumer

      public ClientBuilder addConsumer(BiConsumer<ClientEvent,AgentCard> consumer)
      Add a single event consumer to process events from the agent.

      Consumers receive ClientEvent instances (MessageEvent, TaskEvent, TaskUpdateEvent) along with the agent's AgentCard. Multiple consumers can be registered and will be invoked in registration order.

      Example:

      
       builder.addConsumer((event, card) -> {
           if (event instanceof MessageEvent me) {
               String text = me.getMessage().parts().stream()
                   .filter(p -> p instanceof TextPart)
                   .map(p -> ((TextPart) p).text())
                   .collect(Collectors.joining());
               System.out.println("Agent: " + text);
           }
       });
       
      Parameters:
      consumer - the event consumer to add
      Returns:
      this builder for method chaining
      See Also:
    • addConsumers

      public ClientBuilder addConsumers(List<BiConsumer<ClientEvent,AgentCard>> consumers)
      Add multiple event consumers to process events from the agent.

      Consumers receive ClientEvent instances and are invoked in the order they appear in the list.

      Parameters:
      consumers - the list of event consumers to add
      Returns:
      this builder for method chaining
      See Also:
    • streamingErrorHandler

      public ClientBuilder streamingErrorHandler(Consumer<Throwable> streamErrorHandler)
      Configure an error handler for streaming scenarios.

      This handler is invoked when errors occur during streaming event consumption. It's only applicable when the client and agent both support streaming. For non-streaming scenarios, errors are thrown directly as A2AClientException.

      Example:

      
       builder.streamingErrorHandler(throwable -> {
           if (throwable instanceof A2AClientException e) {
               log.error("A2A error: " + e.getMessage(), e);
           } else {
               log.error("Unexpected error: " + throwable.getMessage(), throwable);
           }
       });
       
      Parameters:
      streamErrorHandler - the error handler for streaming errors
      Returns:
      this builder for method chaining
    • clientConfig

      public ClientBuilder clientConfig(@NonNull ClientConfig clientConfig)
      Configure client behavior such as streaming mode, polling, and transport preference.

      The configuration controls how the client communicates with the agent:

      • Streaming vs blocking mode
      • Polling for updates vs receiving events
      • Client vs server transport preference
      • Output modes, history length, and metadata

      Example:

      
       ClientConfig config = new ClientConfig.Builder()
           .setStreaming(true)  // Enable streaming if server supports it
           .setUseClientPreference(true)  // Use client's transport order
           .setHistoryLength(10)  // Request last 10 messages of context
           .build();
       builder.clientConfig(config);
       
      Parameters:
      clientConfig - the client configuration
      Returns:
      this builder for method chaining
      See Also:
    • build

      public Client build() throws A2AClientException
      Build the configured Client instance.

      This method performs transport negotiation between the client's configured transports and the agent's AgentCard.supportedInterfaces(). The selection algorithm:

      1. If ClientConfig.isUseClientPreference() is true, iterate through client transports in registration order and select the first one the server supports
      2. Otherwise, iterate through server interfaces in preference order (first entry in AgentCard.supportedInterfaces()) and select the first one the client supports

      Important: At least one transport must be configured via withTransport(java.lang.Class<T>, org.a2aproject.sdk.client.transport.spi.ClientTransportConfigBuilder<? extends org.a2aproject.sdk.client.transport.spi.ClientTransportConfig<T>, ?>), otherwise this method throws A2AClientException.

      Returns:
      the configured client instance
      Throws:
      A2AClientException - if no compatible transport is found or if transport configuration is missing