mech.app

The mech.app newsletter

Agentic AI, minus the noise.

Get practical field notes on AI agents, automation, developer tools and security delivered to your inbox.

No spam. Unsubscribe anytime.

AI Agents

Spring AI + Amazon Bedrock: How Java Developers Wire Tool Calling, Memory, and Multi-Agent Orchestration into Production

Examine the production plumbing Java developers need when building autonomous agents with Spring AI and Amazon Bedrock AgentCore.

Source: aws.amazon.com
Spring AI + Amazon Bedrock: How Java Developers Wire Tool Calling, Memory, and Multi-Agent Orchestration into Production

Most agentic AI tutorials target Python developers. AWS just published a detailed walkthrough for Java teams building production agents with Spring AI and Amazon Bedrock. The post exposes orchestration primitives, state management, and deployment boundaries that most quickstarts skip.

This matters because Java shops need to integrate agents into existing Spring Boot services, not rewrite everything in Python. The post shows how Spring AI abstracts tool calling, manages conversation memory, and handles resilience patterns (retries, circuit breakers) before deploying to Amazon Bedrock AgentCore.

Tool Calling Abstraction in Spring AI

Spring AI wraps Bedrock’s function-calling API with a declarative interface. You annotate Java methods with @Tool, and the framework handles JSON schema generation, parameter marshaling, and response parsing.

@Component
public class WeatherTools {
    
    @Tool(description = "Get current weather for a location")
    public WeatherResponse getCurrentWeather(
        @ToolParam(description = "City name") String city,
        @ToolParam(description = "Temperature unit") String unit
    ) {
        // Call external weather API
        return weatherService.fetch(city, unit);
    }
}

The framework generates a JSON schema from method signatures and sends it to Bedrock. When the model decides to call a tool, Spring AI deserializes the arguments, invokes the method, and returns the result to the model for the next reasoning step.

Framework boundary trade-off: Spring AI hides the raw function-calling loop. You gain type safety and automatic schema generation. You lose fine-grained control over retry logic per tool call and cannot easily inspect intermediate tool invocations without adding custom interceptors.

Conversation Memory Persistence

Spring AI provides three memory strategies:

  1. In-memory store: Fast, stateless, lost on restart
  2. Redis-backed store: Shared across instances, requires external dependency
  3. DynamoDB-backed store: AWS-native, durable, higher latency

The post recommends Redis for multi-instance deployments and DynamoDB for serverless patterns. Both implementations use a ConversationMemory interface:

@Bean
public ConversationMemory conversationMemory() {
    return new RedisConversationMemory(redisTemplate);
}

State management risk: If you use in-memory storage with auto-scaling, each instance maintains separate conversation history. Users hitting different instances mid-conversation will lose context. Redis or DynamoDB solve this but introduce network latency on every message.

Memory StrategyLatencyDurabilityMulti-InstanceCost
In-memory<1msNoneNoFree
Redis2-5msHighYesModerate
DynamoDB10-20msHighYesPay-per-request

Resilience Patterns for Multi-Agent Workflows

The post highlights three failure modes in agent orchestration:

  1. Tool call timeout: External API takes too long
  2. Model throttling: Bedrock rate limits kick in
  3. Cascading failures: One agent blocks downstream agents

Spring AI integrates with Resilience4j for circuit breakers and retries:

@Bean
public ChatClient chatClient(BedrockChatModel model) {
    return ChatClient.builder(model)
        .defaultAdvisors(
            new RetryAdvisor(RetryConfig.custom()
                .maxAttempts(3)
                .waitDuration(Duration.ofSeconds(2))
                .build()),
            new CircuitBreakerAdvisor(CircuitBreakerConfig.custom()
                .failureRateThreshold(50)
                .waitDurationInOpenState(Duration.ofSeconds(30))
                .build())
        )
        .build();
}

Cascading failure prevention: Circuit breakers prevent one slow agent from blocking the entire workflow. If an agent fails 50% of requests, the circuit opens for 30 seconds. Downstream agents receive immediate failures instead of waiting for timeouts.

Retry strategy caveat: Retries work for transient Bedrock throttling but can amplify costs if the model consistently returns malformed tool calls. Set a max retry budget per conversation turn, not per tool invocation.

Multi-Agent Orchestration Primitives

Spring AI supports two orchestration patterns:

Sequential chaining: Agent A completes, passes output to Agent B. Simple, predictable, but slow for independent tasks.

Parallel execution: Multiple agents run concurrently, results merge. Faster, but requires conflict resolution if agents modify shared state.

The post demonstrates a customer service workflow:

  1. Triage agent: Classifies incoming request
  2. Specialist agents: Run in parallel based on classification (billing, technical support, account management)
  3. Summary agent: Aggregates specialist outputs into final response
public Mono<AgentResponse> orchestrate(UserRequest request) {
    return triageAgent.classify(request)
        .flatMap(classification -> {
            List<Mono<AgentResponse>> specialists = 
                getSpecialists(classification).stream()
                    .map(agent -> agent.process(request))
                    .collect(Collectors.toList());
            return Mono.zip(specialists, this::merge);
        })
        .flatMap(summaryAgent::summarize);
}

Observability gap: Spring AI does not provide built-in tracing for multi-agent flows. You need to add OpenTelemetry spans manually to track which agent made which tool call and how long each step took.

Amazon Bedrock AgentCore Deployment Model

AgentCore separates orchestration logic from deployment infrastructure. You define agents as Java classes, then deploy them as Lambda functions or ECS tasks. AgentCore handles:

  • Routing: Incoming requests to the correct agent
  • Scaling: Auto-scaling based on queue depth
  • Monitoring: CloudWatch metrics for agent invocations, tool calls, and errors

The deployment boundary looks like this:

User Request → API Gateway → AgentCore Router → Lambda (Agent) → Bedrock API
                                                ↓
                                            DynamoDB (Memory)
                                                ↓
                                            External Tools

Security boundary: Each agent runs in its own Lambda execution role. You grant least-privilege IAM permissions per agent. A billing agent cannot access technical support tools, even if the model hallucinates a tool call.

Cold start trade-off: Lambda-based agents incur 1-3 second cold starts. For latency-sensitive workflows, use provisioned concurrency or deploy agents to ECS Fargate with always-on instances.

Observability and Debugging

The post recommends structured logging for agent decisions:

log.info("Agent decision", Map.of(
    "agent", agentName,
    "tool", toolName,
    "reasoning", modelReasoning,
    "latency_ms", duration.toMillis()
));

CloudWatch Logs Insights queries can then surface:

  • Which tools are called most frequently
  • Which agents have the highest error rates
  • Average latency per agent type

Missing piece: The post does not cover how to replay failed agent interactions. You need to persist the full conversation history (user input, model reasoning, tool calls, tool results) to DynamoDB or S3 for post-mortem debugging.

Technical Verdict

Use Spring AI + Bedrock when:

  • You have existing Spring Boot services and want to add agentic capabilities without rewriting in Python
  • You need type-safe tool definitions and automatic schema generation
  • You want AWS-native deployment with IAM-based security boundaries
  • You can tolerate 10-20ms memory lookup latency for DynamoDB-backed conversation state

Avoid when:

  • You need sub-10ms agent response times (Lambda cold starts and DynamoDB latency will hurt)
  • You require fine-grained control over the tool-calling loop (Spring AI abstracts too much)
  • You need built-in observability for multi-agent workflows (you will build custom tracing)
  • You want to avoid vendor lock-in to AWS infrastructure (AgentCore ties you to Lambda, DynamoDB, and Bedrock)

The framework handles the orchestration plumbing Java developers expect: dependency injection, declarative configuration, and integration with existing Spring patterns. The deployment model (AgentCore) provides clear security boundaries but introduces latency and cold start overhead.