Software Development

MCP for Java Developers: A Practical Tutorial WithSpring AI and the MCP Java SDK

The Model Context Protocol gives AI models a standardized way to call your services, read your data, and use your tools — all without custom glue code. Here is everything a Java developer needs to wire it up correctly with Spring AI 1.0.

What Is MCP and Why Should Java Developers Care?

There is a problem that nearly every AI integration eventually runs into: an LLM is trained on static data, but your application has live information — customer records, inventory, real-time APIs, internal databases. Connecting those two worlds used to mean writing custom tool integrations for every model and every service. That is tedious, fragile, and impossible to standardize.

Model Context Protocol (MCP) solves this by defining a universal, open standard for how AI models talk to external tools and data sources. Introduced by Anthropic in late 2024, MCP is already supported by Claude, Cursor, VS Code Copilot, and a growing list of AI clients. When your service speaks MCP, any compatible AI can use it — without you writing a separate adapter for each one.

For Java developers specifically, the timing is ideal. The official MCP Java SDK launched in December 2024 — maintained in collaboration with the Spring team — and Spring AI 1.0 GA followed, building first-class MCP support directly into the Boot starter ecosystem. In other words, you already have everything you need inside the tools you know.

Official ResourcesThe authoritative references for this tutorial are the MCP Java SDK on GitHub, the Spring AI MCP Getting Started Guide, and the Spring AI Java MCP SDK Reference. All code examples in this article are derived from those sources.

The Architecture: Hosts, Clients, Servers and Primitives

Before touching any code, it is worth spending a moment on the conceptual model, because getting the terminology wrong leads to wiring things up in the wrong direction. MCP uses three distinct roles, and each one has a clear responsibility.

The MCP Host is your main application — the thing that integrates with an LLM and needs external capabilities. It creates and manages one or more MCP Clients internally. The MCP Client maintains a dedicated 1:1 connection to a single MCP Server; it handles protocol negotiation, capability discovery, and message routing. The MCP Server is the lightweight service that exposes your actual functionality.

Servers expose three types of primitives: Tools (executable functions the model can invoke), Resources (data the model can read, like files or database records), and Prompts (reusable prompt templates). Most Spring Boot integrations start with Tools — they are the most immediately practical and the most commonly used primitive.

One of the architectural benefits MCP creates is a clean team boundary: server developers focus on wrapping services and exposing capabilities, while client/AI developers focus on orchestration and model interaction. These can be entirely separate teams, even in separate organizations.

The MCP Java SDK: What It Gives You Out of the Box

The official MCP Java SDK is maintained in collaboration between Anthropic and the Spring team — in fact, the SDK was originally donated to Anthropic by the Spring team. That lineage matters because it means the Spring AI integration is not an afterthought; it is built on the same foundation.

The SDK’s module structure has been refined since its December 2024 launch. As of the current release, the core breakdown looks like this:

ModuleWhat It ContainsUse When
mcp-coreSTDIO, JDK HttpClient, Servlet transport; JSON binding interfacesFramework-agnostic Java applications
mcp-json-jackson2Jackson 2 implementation of JSON bindingProjects still on Jackson 2
mcp-json-jackson3Jackson 3 implementation of JSON bindingSpring Boot 4 / Jackson 3 projects
spring-ai-starter-mcp-serverFull MCP server — STDIO transportLocal processes, Claude Desktop integration
spring-ai-starter-mcp-server-webmvcFull MCP server — SSE + Streamable HTTP via Spring MVCNetwork-accessible servers, synchronous apps
spring-ai-starter-mcp-server-webfluxFull MCP server — SSE + Streamable HTTP via Spring WebFluxReactive applications, async workloads
spring-ai-starter-mcp-clientMCP client — connects to any MCP serverThe AI host application side

The SDK provides both synchronous and asynchronous APIs throughout. The sync API is straightforward for most Spring MVC applications; the async API returns Mono and Flux types and integrates naturally with Spring WebFlux. You choose based on your application’s threading model, not on any MCP-specific requirement.

Transport Types: stdio, SSE, and Streamable HTTP

Transport is where many developers get confused first, so it is worth being precise. MCP defines three transport mechanisms, and the right choice depends entirely on your deployment scenario.

TransportHow It WorksBest ForStatus
stdioJSON-RPC over stdin/stdout streamsLocal tools, Claude Desktop, CLI integrationsStable
SSEHTTP + Server-Sent Events; separate endpoint for messagesLegacy HTTP-based servers; being replacedDeprecated
Streamable HTTPHTTP POST + GET with optional SSE streaming; single endpointProduction network servers; multi-client supportCurrent Standard

The key practical guidance here: if you are building a server that will be deployed over a network and accessed by multiple clients, use Streamable HTTP. Introduced in MCP spec version 2025-03-26, it replaces the older SSE transport and offers cleaner multi-client connection handling. Spring AI 1.1+ supports it directly via spring-ai-starter-mcp-server-webmvc with spring.ai.mcp.server.protocol=STREAMABLE.

If, on the other hand, you are building a tool that runs locally and integrates with Claude Desktop or a similar local AI host, stdio is exactly right. The server runs as a subprocess and communicates through standard streams — simple, reliable, no network required.

When using stdio transport, you must disable Spring Boot’s banner and console logging, otherwise the output will corrupt the MCP stream and the client will fail to parse responses. Add these to your application.propertiesspring.main.banner-mode=off and logging.pattern.console= (empty value).

MCP Transport Usage Trends in Java Projects (2025)

Based on Spring AI community discussions and GitHub project analysis — relative share over time

Building Your First MCP Server with Spring AI

Enough theory. Let’s build something. We will create a simple product catalog MCP server — an endpoint that exposes two tools: one to look up a product by ID and one to search products by category. This is a realistic pattern that applies to almost any domain.

Step 1 — Bootstrap the Project

Head to start.spring.io and create a Spring Boot 3.5+ project (Java 17 minimum, Java 21 recommended). Then add the MCP server dependency to your pom.xml. Use the BOM to avoid version conflicts across Spring AI starters:

  pom.xml — Spring AI BOM + MCP Server Starter

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-bom</artifactId>
      <version>1.0.1</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <!-- MCP Server over HTTP (Streamable HTTP + SSE) -->
  <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
  </dependency>
</dependencies>

Step 2 — Configure the Server

Spring AI’s auto-configuration handles almost everything. You only need a few lines in application.properties to name your server and set the protocol:

  application.properties

spring.application.name=product-catalog-mcp
spring.ai.mcp.server.name=product-catalog
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.type=SYNC
spring.ai.mcp.server.instructions=Provides product catalog lookup and search tools

Step 3 — Define and Register Your Tools

Tools are registered as Spring Beans. The ToolCallbackProvider is the key abstraction: it wraps your service methods and makes them discoverable to MCP clients. The @Tool annotation describes what the tool does (this description is sent to the LLM to help it decide when to call the tool), and @ToolParam describes each parameter:

  ProductCatalogService.java

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;

@Service
public class ProductCatalogService {

    @Tool(description = "Look up a product by its unique identifier")
    public String getProductById(
            @ToolParam(description = "The product ID, e.g. PROD-001") String productId) {
        // In a real app, this queries your database or service layer
        return switch (productId) {
            case "PROD-001" -> "{ \"id\": \"PROD-001\", \"name\": \"Widget Pro\", \"price\": 49.99, \"stock\": 120 }";
            case "PROD-002" -> "{ \"id\": \"PROD-002\", \"name\": \"Gadget Lite\", \"price\": 19.99, \"stock\": 45 }";
            default -> "{ \"error\": \"Product not found\" }";
        };
    }

    @Tool(description = "Search products by category, returns a list of matching products")
    public String searchByCategory(
            @ToolParam(description = "Category name, e.g. electronics, furniture") String category) {
        if ("electronics".equalsIgnoreCase(category)) {
            return "[{\"id\":\"PROD-001\",\"name\":\"Widget Pro\"},{\"id\":\"PROD-002\",\"name\":\"Gadget Lite\"}]";
        }
        return "[]";
    }
}

McpConfig.java — register the tool provider as a Bean

import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class McpConfig {

    @Bean
    public ToolCallbackProvider productCatalogTools(ProductCatalogService service) {
        return MethodToolCallbackProvider.builder()
                .toolObjects(service)
                .build();
    }
}

That is genuinely all the server needs. Spring AI’s auto-configuration picks up the ToolCallbackProvider bean, registers the tools, and starts the MCP endpoint on port 8080. Run the application and your MCP server is live.

Verify It Is WorkingOnce the server starts, you can confirm the MCP endpoint is reachable at http://localhost:8080/mcp. Any MCP-compatible client — including the MCP Inspector tool (npx @modelcontextprotocol/inspector) — can connect and list the tools your server exposes.

Building the MCP Client Side

The client is your AI host application — the Spring Boot service that connects to MCP servers, sends user queries to an LLM, and lets the LLM call tools on those servers. The dependency setup is similarly clean:

  pom.xml — client dependencies

<dependencies>
  <!-- MCP Client -->
  <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client</artifactId>
  </dependency>
  <!-- LLM model — swap for OpenAI, Ollama, etc. -->
  <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-anthropic</artifactId>
  </dependency>
</dependencies>

Then configure which MCP servers the client should connect to. For a Streamable HTTP server running locally:

  application.properties — client config

spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}
spring.ai.anthropic.chat.options.model=claude-opus-4-20250514

# Connect to our product catalog MCP server
spring.ai.mcp.client.streamable-http.connections.product-catalog.url=http://localhost:8080

On the Java side, Spring AI auto-wires a ToolCallbackProvider that holds all tools discovered from all connected MCP servers. You pass that directly into the ChatClient:

  ChatController.java

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.web.bind.annotation.*;

@RestController
public class ChatController {

    private final ChatClient chatClient;
    private final ToolCallbackProvider mcpTools;

    public ChatController(ChatClient.Builder builder, ToolCallbackProvider mcpTools) {
        this.chatClient = builder.build();
        this.mcpTools = mcpTools;
    }

    @GetMapping("/ask")
    public String ask(@RequestParam String question) {
        return chatClient
                .prompt(question)
                .toolCallbacks(mcpTools)  // inject all MCP tools
                .call()
                .content();
    }
}

With this in place, you can call GET /ask?question=What+is+the+price+of+PROD-001? and the LLM will automatically invoke the getProductById tool, retrieve the product data, and compose a natural language response. The tool calling, result handling, and response formatting all happen transparently.

Connecting to External stdio Servers

MCP clients are not limited to your own servers. You can connect to any MCP-compatible server, including the growing ecosystem of open-source TypeScript/Python servers. Connecting to an external stdio server (for example, a web search server) looks like this in application.yml:

  application.yml — stdio connection example

spring:
  ai:
    mcp:
      client:
        stdio:
          connections:
            brave-search:
              command: npx
              args:
                - "-y"
                - "@modelcontextprotocol/server-brave-search"
              env:
                BRAVE_API_KEY: ${BRAVE_API_KEY}

The Annotation Shortcut: @McpTool and @McpToolParam

As of Spring AI 1.1+, there is an even more concise approach available through dedicated MCP annotations. Rather than using the general @Tool and registering via MethodToolCallbackProvider, you can use @McpTool and @McpToolParam directly. Spring’s component scanning picks them up automatically — no manual bean registration needed:

  Annotation-based approach (Spring AI 1.1+)

import io.modelcontextprotocol.annotations.McpTool;
import io.modelcontextprotocol.annotations.McpToolParam;
import org.springframework.stereotype.Service;

@Service
public class WeatherService {

    @McpTool(description = "Get current temperature for a location")
    public String getTemperature(
            @McpToolParam(description = "City name", required = true) String city) {
        // call your weather API here
        return "{ \"city\": \"" + city + "\", \"temp\": \"22°C\", \"condition\": \"Sunny\" }";
    }
}

Version NoteThe @McpTool/@McpToolParam annotation approach and the automatic component scan registration became stable in Spring AI 1.1.0-M2. For Spring AI 1.0.x projects, stick with the @Tool + MethodToolCallbackProvider approach shown in Section 5. Both styles are valid; the annotation approach is simply more concise.

Adoption and Ecosystem Momentum

One of the strongest signals that MCP is worth learning now is the pace of ecosystem adoption. Since the Java SDK launched in December 2024, several major Java and AI frameworks have built on top of it — and the community around it has grown quickly.

MCP Java SDK GitHub Stars Growth (Dec 2024 – Apr 2026)

Source: GitHub — modelcontextprotocol/java-sdk public star history

Beyond the numbers, the contributor list on the MCP Java SDK includes engineers from Broadcom, Oracle, Google, and Apache Pekko — a sign that this is not a single-vendor project. Furthermore, the Spring AI examples repository already contains numerous MCP implementations across different patterns (stdio, SSE, WebFlux, reactive) that you can study and run directly.

“MCP is like a universal adapter between your applications and AI assistants. Instead of building custom integrations for each AI tool, you build one MCP server that any MCP-compatible client can connect to.”— Dan Vega, Spring Developer Advocate, Broadcom

The combination of official Spring support, active SDK maintenance, and a clear migration path from older tool-calling patterns makes this the right moment to adopt MCP in your Java projects — not a research project, but a production-ready approach.

Common Pitfalls and How to Avoid Them

Even with good tooling, certain mistakes show up repeatedly when developers first wire up MCP in Java. Here are the ones worth knowing in advance.

Forgetting to silence logging on stdio servers

Any console output — including the Spring Boot startup banner — corrupts the stdio stream and causes the client to fail with a JSON parse error. Always set spring.main.banner-mode=off and logging.pattern.console= for stdio-transport servers. This is easy to forget and hard to debug without knowing why.

Using SSE transport for new servers

The SSE transport is deprecated as of MCP spec 2025-03-26 and is being phased out. Start new HTTP-based servers with Streamable HTTP (protocol=STREAMABLE) instead. Spring AI 1.1+ makes this the default path, but 1.0.x projects default to SSE — worth double-checking your configuration.

Writing vague tool descriptions

The description in @Tool or @McpTool is not documentation for humans — it is the signal the LLM uses to decide whether and when to invoke your tool. Vague descriptions (“handles product requests”) lead to the model either ignoring your tool or calling it inappropriately. Be specific and action-oriented: “Looks up a product by its SKU identifier and returns current price and stock level.”

Mixing Spring AI 1.0.x and 1.1.x annotations

The @McpTool/@McpToolParam annotations from the MCP annotations module are only stable from Spring AI 1.1+. If you use them in a 1.0.x project without the correct dependency version, the auto-configuration will not pick them up and your tools will silently not register. Stick to the 1.0.x-compatible @Tool approach until you upgrade.

Not testing with the MCP Inspector

Before hooking up a full AI client, validate your server independently. The MCP Inspector (npx @modelcontextprotocol/inspector) lets you connect directly to your server, list tools, and invoke them manually. This quickly reveals registration issues, missing descriptions, or transport misconfigurations without any LLM involved.

Quick Reference: Which Starter Do I Need?

ScenarioDependencyTransportConfig Key
Local tool for Claude Desktop / CLIspring-ai-starter-mcp-serverstdiotype=SYNC
HTTP server, synchronous appspring-ai-starter-mcp-server-webmvcStreamable HTTPprotocol=STREAMABLE
HTTP server, reactive / WebFlux appspring-ai-starter-mcp-server-webfluxStreamable HTTPtype=ASYNC
AI host connecting to MCP serversspring-ai-starter-mcp-clientstdio or HTTPmcp.client.stdio.* or streamable-http.*
Plain Java (no Spring) MCP serverio.modelcontextprotocol.sdk:mcpAll typesManual McpServer.sync()

What We Have Learned

MCP is rapidly becoming the standard integration layer between AI models and the services they need to be genuinely useful. For Java developers, the combination of the official MCP Java SDK (launched December 2024, co-maintained by the Spring team) and Spring AI 1.0‘s Boot starters makes the implementation path remarkably clean. You define tools on a Spring service, register a ToolCallbackProvider bean, and the auto-configuration handles the rest.

Throughout this tutorial, we covered the three-tier architecture (Host → Client → Server), the three transport types and when to use each (stdio for local tools, Streamable HTTP for networked servers), how to build both a server and a client with verified, runnable code, and the annotation-based shortcut available in Spring AI 1.1+. We also walked through the most common pitfalls — particularly the silent logging corruption on stdio servers and the deprecation of the SSE transport.

The bottom line: if you are building any AI-powered feature in a Spring Boot application, MCP is the right abstraction layer. It gives your tools a standardized interface that any future AI client can use, without locking you into any single model or vendor. Start with a single tool, deploy it, test it with the MCP Inspector, and build from there.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button