DEV Community

Cover image for Building RAG Applications with Spring AI: Connect LLMs to Your Own Data
Ayush Shrivastava
Ayush Shrivastava

Posted on

Building RAG Applications with Spring AI: Connect LLMs to Your Own Data

Large Language Models are powerful.

But there is one fundamental limitation:

An LLM doesn't automatically know your application's private data.

Your company policies, product documentation, internal knowledge base, customer records, PDFs, technical documentation, or database content are not necessarily part of the model's training data.

This is where RAG — Retrieval-Augmented Generation comes in.

Instead of asking an LLM to answer directly, we first retrieve relevant information from our own data and provide that information as context to the model.

In this article, we'll build the foundation of a RAG application using Spring AI.


What is RAG?

RAG stands for:

Retrieval-Augmented Generation

The idea is simple:

User Question
      ↓
Retrieve Relevant Information
      ↓
Add Retrieved Context to Prompt
      ↓
LLM
      ↓
Generated Answer
Enter fullscreen mode Exit fullscreen mode

For example, imagine we have a company's internal documentation.

A user asks:

What is our refund policy for annual subscriptions?
Enter fullscreen mode Exit fullscreen mode

Instead of expecting the LLM to magically know the answer, our application:

  1. Searches the company's documents.
  2. Finds the relevant refund-policy content.
  3. Adds that content to the prompt.
  4. Sends the prompt to the LLM.
  5. Generates an answer grounded in the retrieved information.

This is the core idea behind RAG.


Why Do We Need RAG?

A normal LLM application looks like this:

User
  ↓
Application
  ↓
LLM
  ↓
Answer
Enter fullscreen mode Exit fullscreen mode

The problem?

The LLM only has access to the information available to it.

With RAG, the architecture becomes:

                  ┌──────────────┐
                  │  Documents   │
                  └──────┬───────┘
                         ↓
                    Chunking
                         ↓
                    Embeddings
                         ↓
                  Vector Database
                         ↑
                         │
User → Query → Similarity Search
                         ↓
                  Relevant Context
                         ↓
                        LLM
                         ↓
                      Answer
Enter fullscreen mode Exit fullscreen mode

Now the model can work with information from our own knowledge base.


The RAG Pipeline

A production RAG pipeline typically contains these stages:

Documents
    ↓
Document Loading
    ↓
Chunking
    ↓
Embeddings
    ↓
Vector Database
    ↓
Similarity Search
    ↓
Relevant Context
    ↓
Prompt
    ↓
LLM
    ↓
Answer
Enter fullscreen mode Exit fullscreen mode

Let's understand each step.


1. Document Loading

First, we need to get our data into the application.

The source could be:

  • PDF files
  • Markdown files
  • HTML pages
  • Word documents
  • Database records
  • APIs
  • Knowledge bases
  • Internal documentation

Spring AI provides abstractions for working with documents.

A document can be represented using Spring AI's Document abstraction.

Conceptually:

Document document = new Document(
    "Spring Boot is a framework for building Java applications..."
);
Enter fullscreen mode Exit fullscreen mode

We now have content that can be processed by our RAG pipeline.


2. Chunking

We shouldn't usually store an entire document as a single vector.

Imagine a 100-page PDF.

A user asks:

How do I configure authentication?
Enter fullscreen mode Exit fullscreen mode

We don't want to retrieve the entire PDF.

Instead, we split the document into smaller pieces called chunks.

For example:

Document
   ↓
Chunk 1
Chunk 2
Chunk 3
Chunk 4
...
Chunk 100
Enter fullscreen mode Exit fullscreen mode

Each chunk represents a smaller piece of knowledge.

A simple example:

Chunk 1:
Introduction to Spring Security

Chunk 2:
Configuring authentication

Chunk 3:
Creating users

Chunk 4:
JWT authentication

Chunk 5:
Role-based authorization
Enter fullscreen mode Exit fullscreen mode

Now when the user asks about JWT authentication, we can retrieve the relevant chunk instead of the entire document.


3. Embeddings

This is where things get interesting.

A computer doesn't understand the semantic meaning of text in the same way humans do.

We need a way to represent text numerically.

That's where embeddings come in.

An embedding model converts text into a vector.

For example:

"How do I configure JWT authentication?"
Enter fullscreen mode Exit fullscreen mode

might become something conceptually like:

[0.12, -0.42, 0.87, 0.31, ...]
Enter fullscreen mode Exit fullscreen mode

The actual vector contains many dimensions.

The important part is:

Semantically similar text produces vectors that are relatively close together in vector space.

For example:

"How can I configure JWT?"
        ↓
[0.12, 0.81, 0.42, ...]

"JWT authentication configuration"
        ↓
[0.15, 0.78, 0.45, ...]
Enter fullscreen mode Exit fullscreen mode

These vectors should have high similarity.


4. Vector Database

Now we need somewhere to store these embeddings.

That's where a vector database comes in.

Popular choices include:

  • PostgreSQL + pgvector
  • Pinecone
  • Weaviate
  • Qdrant
  • Milvus
  • Elasticsearch
  • Redis

For a Spring Boot application, PostgreSQL with pgvector is an especially interesting option because you can keep your relational data and vector data within the same ecosystem.

Conceptually:

Document Chunk
      ↓
Embedding Model
      ↓
Vector
      ↓
Vector Database
Enter fullscreen mode Exit fullscreen mode

Our database might contain something conceptually like:

ID | Content                    | Embedding
---|----------------------------|----------------
1  | JWT configuration...       | [0.12,...]
2  | OAuth2 configuration...    | [0.42,...]
3  | Database configuration...  | [0.71,...]
Enter fullscreen mode Exit fullscreen mode

5. Similarity Search

Now suppose the user asks:

How do I configure JWT authentication in Spring Boot?
Enter fullscreen mode Exit fullscreen mode

We first generate an embedding for the question.

User Query
    ↓
Embedding Model
    ↓
Query Vector
Enter fullscreen mode Exit fullscreen mode

Then we search the vector database for similar vectors.

Query Vector
     ↓
Vector Database
     ↓
Similarity Search
     ↓
Top K Relevant Chunks
Enter fullscreen mode Exit fullscreen mode

For example:

Result 1 → JWT configuration
Result 2 → Spring Security authentication
Result 3 → SecurityFilterChain configuration
Enter fullscreen mode Exit fullscreen mode

These results become our context.


6. Building the Prompt

Now we combine:

  • User question
  • Retrieved context

For example:

Context:

Spring Security can be configured using SecurityFilterChain.
JWT authentication can be implemented using a custom
authentication filter...

Question:

How do I configure JWT authentication in Spring Boot?
Enter fullscreen mode Exit fullscreen mode

The LLM receives this information and generates the answer.

This is the Augmented part of Retrieval-Augmented Generation.


7. Sending Context to the LLM

The final flow looks like:

User Question
      ↓
Embedding
      ↓
Vector Search
      ↓
Relevant Documents
      ↓
Prompt + Context
      ↓
LLM
      ↓
Answer
Enter fullscreen mode Exit fullscreen mode

The LLM isn't searching the database itself.

Our application retrieves the information first and provides it to the model.


Building RAG with Spring AI

Now let's look at how Spring AI simplifies this architecture.

A typical Spring AI RAG application contains:

Spring Boot
     │
     ├── Document Reader
     │
     ├── Text Splitter
     │
     ├── Embedding Model
     │
     ├── Vector Store
     │
     └── Chat Model
Enter fullscreen mode Exit fullscreen mode

The exact model and vector store can be swapped without rewriting the entire application.

That's one of the strengths of Spring AI's abstraction-based approach.


Project Setup

Let's create a Spring Boot project.

We'll need Spring AI dependencies for:

  • Chat model
  • Embeddings
  • Vector store

For example, with Maven:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-bedrock-converse</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

You should use versions compatible with the Spring AI version used by your project.


Configuring the Model

If we're using AWS Bedrock, our application needs AWS credentials and model configuration.

Conceptually:

spring:
  ai:
    bedrock:
      aws:
        region: us-east-1
Enter fullscreen mode Exit fullscreen mode

The exact configuration depends on the Spring AI version and Bedrock model you're using.

For production environments, don't hard-code AWS credentials inside application.yml.

Use:

Environment Variables
        ↓
IAM Roles
        ↓
AWS Credentials Provider Chain
Enter fullscreen mode Exit fullscreen mode

Whenever possible, prefer IAM roles over static credentials.


Creating a Vector Store

For PostgreSQL + pgvector, the architecture looks like:

Spring Boot
     ↓
Spring AI VectorStore
     ↓
PostgreSQL
     ↓
pgvector
Enter fullscreen mode Exit fullscreen mode

The vector store becomes the bridge between our application and the vector database.

Conceptually:

VectorStore vectorStore;
Enter fullscreen mode Exit fullscreen mode

We can then add documents:

vectorStore.add(documents);
Enter fullscreen mode Exit fullscreen mode

And later search:

List<Document> results =
        vectorStore.similaritySearch(
                SearchRequest.builder()
                        .query(question)
                        .topK(5)
                        .build()
        );
Enter fullscreen mode Exit fullscreen mode

The important abstraction here is:

VectorStore
Enter fullscreen mode Exit fullscreen mode

Our application doesn't need to manually implement vector similarity calculations.


Loading Documents

Let's say we have a PDF containing product documentation.

Spring AI provides document readers that can load different document formats.

Conceptually:

var documents = reader.get();
Enter fullscreen mode Exit fullscreen mode

We then split the documents into chunks.

For example:

TokenTextSplitter splitter = new TokenTextSplitter();

List<Document> chunks =
        splitter.apply(documents);
Enter fullscreen mode Exit fullscreen mode

The exact splitter and configuration should be chosen based on your document structure and model context window.


Creating Embeddings

The embedding model converts every chunk into a vector.

Conceptually:

Document Chunk
      ↓
Embedding Model
      ↓
Vector
Enter fullscreen mode Exit fullscreen mode

Spring AI handles this through the embedding/vector-store integration.

Then we can store the documents:

vectorStore.add(chunks);
Enter fullscreen mode Exit fullscreen mode

Our ingestion pipeline is now:

PDF
 ↓
Documents
 ↓
Chunks
 ↓
Embeddings
 ↓
Vector Store
Enter fullscreen mode Exit fullscreen mode

Retrieval

Now let's handle the user's question.

Suppose the user asks:

How does authentication work in our application?
Enter fullscreen mode Exit fullscreen mode

We search the vector store:

List<Document> results =
        vectorStore.similaritySearch(
                SearchRequest.builder()
                        .query(question)
                        .topK(5)
                        .build()
        );
Enter fullscreen mode Exit fullscreen mode

We now have the most relevant pieces of information.

For example:

Result 1
JWT authentication uses...

Result 2
The authentication filter...

Result 3
Security configuration...
Enter fullscreen mode Exit fullscreen mode

Building the RAG Prompt

We can now construct a prompt using the retrieved documents.

For example:

String context = results.stream()
        .map(Document::getText)
        .collect(Collectors.joining("\n\n"));

String prompt = """
        Answer the question using only the provided context.

        Context:
        %s

        Question:
        %s
        """.formatted(context, question);
Enter fullscreen mode Exit fullscreen mode

Then send it to the chat model.

Conceptually:

ChatResponse response =
        chatModel.call(
                new Prompt(prompt)
        );
Enter fullscreen mode Exit fullscreen mode

And we return:

response.getResult()
        .getOutput()
        .getText();
Enter fullscreen mode Exit fullscreen mode

That's a basic RAG implementation.


The Complete Flow

Putting everything together:

                 INGESTION PIPELINE

              ┌───────────────┐
              │   Documents   │
              └───────┬───────┘
                      ↓
                Text Splitting
                      ↓
                 Embeddings
                      ↓
               Vector Store
                      │
                      │
                      │
                      ▼
                 RETRIEVAL
                      ▲
                      │
               User Question
                      ↓
                  Embedding
                      ↓
               Similarity Search
                      ↓
                Top K Chunks
                      ↓
                   Context
                      ↓
                    Prompt
                      ↓
                     LLM
                      ↓
                   Answer
Enter fullscreen mode Exit fullscreen mode

This is the basic architecture behind many modern knowledge-based AI applications.


Why Chunking Matters

One of the most underestimated parts of RAG is chunking.

Bad chunking can produce bad retrieval.

Imagine this document:

Spring Security

Authentication allows the application to verify
the identity of a user.

Authorization determines whether the authenticated
user has permission to access a resource.
Enter fullscreen mode Exit fullscreen mode

If we split this badly:

Chunk 1:
Authentication allows...

Chunk 2:
the identity of a user. Authorization determines...

Chunk 3:
whether the authenticated user...
Enter fullscreen mode Exit fullscreen mode

we may destroy important semantic relationships.

A better strategy is to preserve meaningful boundaries where possible.

Depending on the data, you may experiment with:

Chunk Size
Overlap
Sentence boundaries
Paragraph boundaries
Markdown headings
Semantic sections
Enter fullscreen mode Exit fullscreen mode

There is no universal chunk size that works for every RAG application.


Why Top-K Matters

When performing similarity search, we usually retrieve the top K results.

For example:

.topK(5)
Enter fullscreen mode Exit fullscreen mode

means:

Return the 5 most relevant chunks.
Enter fullscreen mode Exit fullscreen mode

But bigger isn't always better.

If we retrieve too little:

K = 1
Enter fullscreen mode Exit fullscreen mode

we may miss important context.

If we retrieve too much:

K = 50
Enter fullscreen mode Exit fullscreen mode

we may introduce irrelevant information and increase token usage.

A common approach is to start with a small value and evaluate retrieval quality.

For example:

K = 3
K = 5
K = 10
Enter fullscreen mode Exit fullscreen mode

Then measure which configuration works best for your dataset.


RAG vs Fine-Tuning

A common question is:

Why not fine-tune the model instead?

RAG and fine-tuning solve different problems.

RAG

Best when:

  • Knowledge changes frequently
  • You have private documents
  • You need citations or traceability
  • You need to update knowledge without retraining
  • You want to retrieve specific information

Fine-Tuning

Useful when you want to change:

  • Model behavior
  • Output style
  • Domain-specific patterns
  • Task performance

A useful mental model is:

RAG
→ Give the model the right information.

Fine-tuning
→ Change how the model behaves.
Enter fullscreen mode Exit fullscreen mode

In many production systems, they can also be used together.


RAG Is More Than Vector Search

A basic RAG pipeline is only the beginning.

Production RAG systems often introduce additional stages:

Query
 ↓
Query Transformation
 ↓
Hybrid Retrieval
 ↓
Metadata Filtering
 ↓
Vector Search
 ↓
Reranking
 ↓
Context Compression
 ↓
Prompt Construction
 ↓
LLM
Enter fullscreen mode Exit fullscreen mode

You might eventually introduce techniques such as:

  • Hybrid search
  • Metadata filtering
  • Reranking
  • HyDE
  • Query rewriting
  • Self-RAG
  • Corrective RAG
  • Graph RAG
  • Agentic RAG

This is where RAG becomes an engineering discipline rather than simply "put documents into a vector database."


Production Considerations

If you're building RAG for production, don't stop at:

PDF → Vector DB → LLM
Enter fullscreen mode Exit fullscreen mode

You also need to think about:

1. Document ingestion

How frequently are documents updated?

New document
     ↓
Process
     ↓
Chunk
     ↓
Embed
     ↓
Update Vector Store
Enter fullscreen mode Exit fullscreen mode

2. Metadata

Store useful metadata alongside chunks:

document_id
source
page_number
tenant_id
created_at
updated_at
document_type
Enter fullscreen mode Exit fullscreen mode

This becomes extremely useful for filtering.

For example:

tenant_id = "company-123"
Enter fullscreen mode Exit fullscreen mode

can ensure that users only retrieve documents belonging to their tenant.

3. Access Control

This is critical.

A RAG system must not retrieve documents that the current user isn't authorized to access.

Your retrieval layer should respect application permissions.

User
 ↓
Authentication
 ↓
Authorization
 ↓
Metadata Filters
 ↓
Retrieval
 ↓
LLM
Enter fullscreen mode Exit fullscreen mode

Never assume that because the LLM can't "see" a document directly, the document is secure.

4. Observability

Track things such as:

Retrieval latency
Embedding latency
LLM latency
Token usage
Retrieved chunks
Similarity scores
Failure rate
Answer quality
Enter fullscreen mode Exit fullscreen mode

Without observability, debugging RAG becomes extremely difficult.


A Simple Mental Model

If you're new to RAG, remember this:

Embedding Model
    =
    "Convert meaning into numbers"

Vector Database
    =
    "Store and search those meanings"

Retriever
    =
    "Find relevant information"

LLM
    =
    "Use that information to generate an answer"
Enter fullscreen mode Exit fullscreen mode

Together:

Retrieve → Augment → Generate
Enter fullscreen mode Exit fullscreen mode

That's RAG.


Where Spring AI Fits

Spring AI gives Java developers abstractions around many of these building blocks.

Instead of manually wiring every AI provider and vector database integration, we can work with abstractions such as:

ChatModel
EmbeddingModel
VectorStore
Document
DocumentReader
Enter fullscreen mode Exit fullscreen mode

This allows us to focus more on the application architecture rather than provider-specific implementation details.

And that's particularly useful when building enterprise Java applications where we may want to change:

AWS Bedrock
     ↓
Another Model Provider
Enter fullscreen mode Exit fullscreen mode

without completely rewriting our application.


Final Architecture

A production-oriented Spring AI RAG application can eventually look like this:

                    ┌───────────────────┐
                    │    User / App     │
                    └─────────┬─────────┘
                              ↓
                       Spring Boot API
                              ↓
                       Query Processing
                              ↓
                       Retrieval Layer
                              ↓
                    ┌───────────────────┐
                    │   Vector Store    │
                    │   PostgreSQL      │
                    │    + pgvector     │
                    └─────────┬─────────┘
                              ↓
                     Relevant Context
                              ↓
                      Prompt Assembly
                              ↓
                    ┌───────────────────┐
                    │    Spring AI     │
                    └─────────┬─────────┘
                              ↓
                    ┌───────────────────┐
                    │   AWS Bedrock    │
                    └─────────┬─────────┘
                              ↓
                           Answer
Enter fullscreen mode Exit fullscreen mode

And separately:

Documents
    ↓
Document Reader
    ↓
Chunking
    ↓
Embedding Model
    ↓
PostgreSQL + pgvector
Enter fullscreen mode Exit fullscreen mode

What's Next?

We started this series by exploring how Spring AI can connect Java applications with modern AI models.

With AWS Bedrock, we can access powerful foundation models without managing the underlying model infrastructure.

Now, with RAG, we can take another major step:

We can connect those models to our own data.

The journey now looks like:

LLM
 ↓
Spring AI
 ↓
AWS Bedrock
 ↓
RAG
 ↓
Vector Database
 ↓
Our Own Data
Enter fullscreen mode Exit fullscreen mode

But there is another important capability missing.

What if we don't just want the model to answer questions?

What if we want the model to take actions?

For example:

User
 ↓
AI Agent
 ↓
Decide what to do
 ↓
Call a Tool
 ↓
Execute Action
 ↓
Return Result
Enter fullscreen mode Exit fullscreen mode

That's where tool calling and AI agents come in.

Next up: Building AI Agents with Spring AI — Tool Calling, Memory, and Autonomous Workflows.


Key Takeaways

  • RAG connects LLMs to external knowledge.
  • Documents are split into smaller chunks.
  • Embeddings convert text into vectors.
  • Vector databases store and retrieve those vectors.
  • Similarity search finds relevant information.
  • Retrieved context is added to the LLM prompt.
  • Spring AI provides abstractions for building these pipelines.
  • PostgreSQL + pgvector is a practical option for Java applications.
  • Production RAG requires security, metadata filtering, observability, and evaluation.
  • RAG is not just vector search — retrieval quality ultimately determines answer quality.

If you're building AI applications with Java and Spring Boot, RAG is one of the most important patterns to understand.

Top comments (1)

Collapse
 
crdtcto profile image
Kane Lim

Hello Glad to see you, I am Kane Lim from Hong Kong. I have over 10 years of development experience. I am writing this because your post was interesting.

This is a solid foundation, especially the emphasis on authorization and observability. I would push the architecture further by treating retrieval as a measurable subsystem rather than simply a vector lookup.

For production RAG, I would combine metadata constrained hybrid retrieval with BM25 plus embeddings, followed by a cross encoder reranker and context compression. Store document version, tenant, ACL, source, section, timestamps, and content hashes so incremental ingestion can remain idempotent.

The evaluation layer is equally important. Build a golden dataset containing questions, expected evidence, and acceptable answers, then measure Recall@K, MRR, groundedness, citation accuracy, latency, and token consumption independently. This quickly exposes whether failures originate in retrieval, prompt construction, or generation.

Spring AI's abstractions make this especially interesting because the retrieval and model layers can evolve independently. A strong architecture here could become a reusable enterprise RAG platform rather than a single application.

I would enjoy exchanging ideas around evaluation and retrieval architecture.