← Blog

Claude API Java Spring Boot Setup Guide

2026-10-01 · 4 min read · SubToAPI Team

Setting up the Claude API in a Spring Boot project means configuring an HTTP client bean, modeling the request/response JSON as Java records or POJOs, and wiring your API key through Spring's configuration properties. This guide walks through a complete setup using WebClient (the recommended modern client), with a RestTemplate alternative for older codebases that haven't migrated yet.

The core challenge isn't Spring-specific — it's that you need an HTTP client, JSON serialization that matches Anthropic's message schema, and proper handling of streaming responses if you want them. Below is a working setup you can drop into any Spring Boot 3.x project.

Project Dependencies

You don't need Anthropic's SDK — a plain HTTP client is enough since the API is just REST + JSON. Add these to your pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

spring-boot-starter-webflux gives you WebClient, which handles streaming far better than RestTemplate.

Configuration Properties

Keep your API key out of source control. In application.yml:

claude:
  api-key: ${CLAUDE_API_KEY}
  base-url: https://api.anthropic.com/v1
  model: claude-3-5-sonnet-20241022

Bind it with a configuration class:

@ConfigurationProperties(prefix = "claude")
public record ClaudeProperties(String apiKey, String baseUrl, String model) {}

Enable it in your main application class or a @Configuration class with @EnableConfigurationProperties(ClaudeProperties.class).

WebClient Bean

@Configuration
public class ClaudeClientConfig {

    @Bean
    public WebClient claudeWebClient(ClaudeProperties props) {
        return WebClient.builder()
            .baseUrl(props.baseUrl())
            .defaultHeader("x-api-key", props.apiKey())
            .defaultHeader("anthropic-version", "2023-06-01")
            .defaultHeader("content-type", "application/json")
            .build();
    }
}

Request and Response Models

Model the message schema with Java records — immutable, concise, and perfect for DTOs:

public record ClaudeMessage(String role, String content) {}

public record ClaudeRequest(
    String model,
    int max_tokens,
    List<ClaudeMessage> messages
) {}

public record ClaudeResponse(
    String id,
    String role,
    List<ContentBlock> content,
    Usage usage
) {
    public record ContentBlock(String type, String text) {}
    public record Usage(int input_tokens, int output_tokens) {}
}

The Service Class

@Service
public class ClaudeService {

    private final WebClient webClient;
    private final ClaudeProperties props;

    public ClaudeService(WebClient claudeWebClient, ClaudeProperties props) {
        this.webClient = claudeWebClient;
        this.props = props;
    }

    public ClaudeResponse sendMessage(String prompt) {
        ClaudeRequest request = new ClaudeRequest(
            props.model(),
            1024,
            List.of(new ClaudeMessage("user", prompt))
        );

        return webClient.post()
            .uri("/messages")
            .bodyValue(request)
            .retrieve()
            .bodyToMono(ClaudeResponse.class)
            .block();
    }
}

Expose it through a controller:

@RestController
@RequestMapping("/api/chat")
public class ChatController {

    private final ClaudeService claudeService;

    public ChatController(ClaudeService claudeService) {
        this.claudeService = claudeService;
    }

    @PostMapping
    public ClaudeResponse chat(@RequestBody Map<String, String> body) {
        return claudeService.sendMessage(body.get("prompt"));
    }
}

This gives you a working /api/chat endpoint that proxies prompts to Claude and returns the parsed response.

Handling Streaming in Spring

If you want token-by-token streaming, use WebClient's reactive bodyToFlux against a server-sent events stream instead of bodyToMono, and return a Flux<String> from a @GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE) endpoint. This requires parsing SSE frames yourself, tracking content_block_delta events, and reassembling partial JSON — doable, but it's a meaningful chunk of extra code that has nothing to do with your actual business logic.

Error Handling and Retries

Claude's API returns standard HTTP error codes — 401 for bad keys, 429 for rate limits, 529 for overload. Wrap calls with onStatus in WebClient:

.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, resp ->
    resp.bodyToMono(String.class).map(body ->
        new ResponseStatusException(resp.statusCode(), body)))
.onStatus(HttpStatusCode::is5xxServerError, resp ->
    Mono.error(new ServiceUnavailableException("Claude API unavailable")))

Pair this with Spring Retry (@Retryable) on 429/529 responses using exponential backoff, since Anthropic's rate limits are per-organization and bursts are common in production traffic.

Simplifying the Setup

All of this — client configuration, DTO modeling, streaming parsers, retry logic, key rotation — is boilerplate every Java team writing against Claude ends up rebuilding. If you'd rather skip straight to calling a stable REST endpoint, SubToAPI wraps your Claude access behind a single HTTPS API with application keys (sub_live_...), so your Spring service just points at https://api.subtoapi.app/v1/messages instead of managing Anthropic auth headers directly:

WebClient.builder()
    .baseUrl("https://api.subtoapi.app/v1")
    .defaultHeader("Authorization", "Bearer " + subToApiKey)
    .build();

This gives you streaming, tool use, and usage metadata without hand-rolling SSE parsing in your controller layer. Check /docs/quickstart for the request format and /docs/streaming for the SSE details, or compare /pricing if you're managing multiple developer seats under one Claude subscription.

questions

Do I need Anthropic's official SDK to use Claude in a Spring Boot app? No. Anthropic doesn't publish an official Java SDK, so most Spring Boot integrations use WebClient or RestTemplate directly against the REST API, as shown above.

Should I use RestTemplate or WebClient for Claude API calls? WebClient is strongly preferred — it's non-blocking, integrates cleanly with Spring's reactive stack, and is the only practical option if you plan to support streaming responses later.

How do I keep my Claude API key secure in a Spring Boot deployment? Load it from environment variables or a secrets manager (AWS Secrets Manager, Vault) via @ConfigurationProperties, never hardcode it in application.yml, and exclude .env files from version control.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →