Building Enterprise AI with Spring AI and Java: RAG and Vector Database Guide

AI tutorial - IT technology blog
AI tutorial - IT technology blog

Enterprise AI Integration: The Python Bridge Dilemma

Nearly every GenAI tutorial begins in Python. When our engineering team had to add internal document search and conversational query capabilities to our core ERP platform, we followed that exact blueprint. We deployed a FastAPI microservice running LangChain alongside our Spring Boot 3 backend. Within three weeks, the cracks were impossible to ignore.

Every query demanded cross-runtime acrobatics. Our Java services had to serialize JPA entity graphs into JSON, transmit them over HTTP, wait for Python to generate embeddings, and deserialize the payload. P99 latency hovered around 820ms.

In addition, keeping DTO schemas synchronized across two separate codebases created continuous deployment headaches. We had to maintain two separate CI/CD pipelines, track disjointed Prometheus metrics, and pass JWT auth headers through an extra hop. Instead of treating AI as a natural extension of our application, we had built an operational silo.

Spring AI eliminates this divide. It introduces native Java primitives for LLM orchestration, document ingestion, and vector store operations directly into the Spring framework.

Approach Comparison: Native Spring AI vs. Polyglot Python Sidecar

When integrating Large Language Models and Retrieval-Augmented Generation (RAG) into an established Java ecosystem, teams face a foundational architectural choice:

Dimension Polyglot Setup (Spring Boot + Python/FastAPI) Native Setup (Spring Boot + Spring AI)
Architecture Dual-service model over REST/gRPC (adds 40–70ms inter-service latency) Single in-process runtime (<1ms invocation overhead)
Resource Footprint Minimum 2 pods (JVM + 1.5GB Python container) Unified JVM container (saving 30–40% cluster RAM)
Type Safety Manual DTO mappings across HTTP boundaries End-to-end compile-time Java type checking
Observability Fragmented traces split between OpenTelemetry and Python loggers Single-pane Micrometer metrics and distributed tracing out of the box
Deployment Dual Dockerfiles, separate Helm charts, coordinated releases Standard Spring Boot executable JAR or single container image

Pros & Cons of Each Architecture

Polyglot Sidecar (Python + LangChain / LlamaIndex)

  • Pros: Instant access to bleeding-edge research papers within days of publication, plus a massive catalog of community recipes.
  • Cons: Tangible network penalties on every retrieval, duplicated data models, an expanded CVE attack surface, and split team focus across two programming paradigms.

Native Java Stack (Spring Boot + Spring AI)

  • Pros: First-class dependency injection, seamless transactional database boundaries, uniform Spring Security context propagation, and zero cross-process network serialization.
  • Cons: Emerging community algorithms typically reach Java a few months after debuting in Python.

When we migrated our production knowledge base of over 180,000 internal documents to native Spring AI, the impact was immediate. Eliminating inter-service hops cut average retrieval time from 580ms to 195ms. At the same time, shutting down the Python pods shaved 12GB of RAM from our Kubernetes cluster. Best of all, our Java developers could write, test, and debug RAG pipelines using the standard Spring patterns they have relied on for years.

Recommended Setup

For an enterprise-ready foundation, deploy the following production-tested stack:

  • Runtime: Java 21 LTS with Spring Boot 3.3+
  • AI Framework: Spring AI 1.0.0-M4 or newer
  • Vector Database: PostgreSQL 16 with the pgvector 0.7+ extension (leveraging your existing relational infrastructure)
  • Model Provider: OpenAI API (gpt-4o-mini for cost-effective reasoning) or self-hosted Ollama for isolated local testing

Spin up your local vector store with Docker in seconds:

docker run -d \
  --name postgres-vector \
  -e POSTGRES_DB=knowledgebase \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=secret \
  -p 5432:5432 \
  pgvector/pgvector:pg16

Implementation Guide: Building a Production-Grade RAG Service

1. Configure Project Dependencies

Add the Spring AI Bill of Materials (BOM) and required starters to your pom.xml:

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

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
    </dependency>
</dependencies>

2. Configure Application Properties

Define database connectivity, vector index parameters, and your model credentials in src/main/resources/application.properties:

spring.application.name=enterprise-ai-service

# OpenAI Configuration
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.options.model=gpt-4o-mini
spring.ai.openai.chat.options.temperature=0.2

# PostgreSQL with PgVector
spring.datasource.url=jdbc:postgresql://localhost:5432/knowledgebase
spring.datasource.username=postgres
spring.datasource.password=secret

# Spring AI PgVector Configuration
spring.ai.vectorstore.pgvector.initialize-schema=true
spring.ai.vectorstore.pgvector.index-type=HNSW
spring.ai.vectorstore.pgvector.distance-type=COSINE_DISTANCE
spring.ai.vectorstore.pgvector.dimensions=1536

3. Ingesting Enterprise Knowledge into the Vector Database

Before your model can answer questions about proprietary data, you need to parse source documents, split them into digestible token chunks, compute vector embeddings, and store them in PostgreSQL. Here is a production-ready ingestion service configured to split policy documents into 800-token blocks with a 350-character window:

package com.itfromzero.ai.service;

import org.springframework.ai.document.Document;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class IngestionService {

    private final VectorStore vectorStore;

    @Value("classpath:docs/enterprise-policy.txt")
    private Resource policyDocument;

    public IngestionService(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    public void loadKnowledgeBase() {
        TextReader textReader = new TextReader(policyDocument);
        List<Document> rawDocuments = textReader.get();

        // 800 token chunk size, 350 min characters per chunk, 5 token overlap margin
        TokenTextSplitter splitter = new TokenTextSplitter(800, 350, 5, 10000, true);
        List<Document> chunks = splitter.apply(rawDocuments);

        vectorStore.accept(chunks);
    }
}

4. Implementing the RAG Chatbot Service

Retrieval-Augmented Generation relies on a straightforward mechanism: query the vector store for semantic matches, inject those snippets into the prompt as factual context, and let the LLM generate the response. Spring AI’s fluent ChatClient and QuestionAnswerAdvisor handle this entire lifecycle in a single bean configuration:

package com.itfromzero.ai.service;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;

@Service
public class SupportBotService {

    private final ChatClient chatClient;

    public SupportBotService(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) {
        this.chatClient = chatClientBuilder
                .defaultAdvisors(new QuestionAnswerAdvisor(
                        vectorStore,
                        SearchRequest.builder().topK(4).similarityThreshold(0.7).build()
                ))
                .defaultSystem("You are a helpful IT enterprise assistant. " +
                        "Answer questions strictly based on the provided context. " +
                        "If the context does not contain the answer, say you do not know.")
                .build();
    }

    public String askQuestion(String userPrompt) {
        return this.chatClient.prompt()
                .user(userPrompt)
                .call()
                .content();
    }
}

5. Exposing the REST API Controller

With the advisor wired into your service, exposing the conversational endpoint takes fewer than twenty lines of code:

package com.itfromzero.ai.controller;

import com.itfromzero.ai.service.SupportBotService;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

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

    private final SupportBotService supportBotService;

    public ChatController(SupportBotService supportBotService) {
        this.supportBotService = supportBotService;
    }

    @PostMapping("/chat")
    public Map<String, String> chat(@RequestBody Map<String, String> payload) {
        String query = payload.get("message");
        String reply = supportBotService.askQuestion(query);
        return Map.of("response", reply);
    }
}

6. Verification

Test your live endpoint using curl:

curl -X POST http://localhost:8080/api/v1/ai/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "What is our company remote work policy?"}'

Behind the scenes, Spring AI queries PostgreSQL using an HNSW cosine similarity scan, filters documents meeting the 0.7 similarity threshold, and constructs the augmented prompt. The model responds within 350ms, grounded entirely in your private corporate data.

Share: