Building a RAG Pipeline with pgvector and Spring Boot
Introduction
In the rapidly evolving landscape of AI, Retrieval-Augmented Generation (RAG) has emerged as a powerful pattern to enhance language models with domain-specific knowledge. Instead of fine-tuning a model (which is expensive and static), RAG allows you to retrieve relevant documents at query time and feed them to the LLM as context. This approach improves accuracy, reduces hallucinations, and keeps your knowledge base up-to-date without retraining.
One of the most elegant ways to implement RAG is using pgvector, a PostgreSQL extension that adds vector similarity search capabilities. Combined with Spring Boot, you get a robust, scalable, and production-ready solution that leverages your existing database infrastructure.
In this comprehensive guide, I’ll walk you through building a complete RAG pipeline from scratch using Spring Boot and pgvector. We’ll cover:
- Setting up PostgreSQL with pgvector
- Generating embeddings using OpenAI’s API (or any compatible model)
- Storing and indexing vectors efficiently
- Implementing similarity search with Spring Data JPA
- Exposing a REST API for querying documents
- Best practices for production deployment
By the end, you’ll have a working RAG system that can answer questions based on your own documents.
Prerequisites
Before we dive in, ensure you have the following:
- Java 17+ and Maven 3.8+
- PostgreSQL 13+ with pgvector extension installed
- An OpenAI API key (or any embedding model endpoint)
- Basic familiarity with Spring Boot and JPA
1. Setting Up PostgreSQL with pgvector
First, install the pgvector extension. On Ubuntu/Debian, you can use:
1 | sudo apt-get install postgresql-14-pgvector |
For other platforms, refer to the official pgvector documentation.
Then, enable the extension in your database:
1 | CREATE EXTENSION IF NOT EXISTS vector; |
Now, let’s create a table to store our documents and their embeddings. We’ll design it to hold both the original text and the vector representation:
1 | CREATE TABLE documents ( |
Note: The vector dimension must match your embedding model. OpenAI’s text-embedding-ada-002 produces 1536 dimensions. If you use a different model, adjust accordingly.
For production, consider using HNSW index for better performance at scale:
1 | CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops); |
2. Project Setup
Create a new Spring Boot project using Spring Initializr with the following dependencies:
- Spring Web
- Spring Data JPA
- PostgreSQL Driver
- Validation
Add the pgvector JDBC support to your pom.xml:
1 | <dependency> |
Also, include the OpenAI Java client (optional, but handy):
1 | <dependency> |
Configure your application.properties:
1 | spring.datasource.url=jdbc:postgresql://localhost:5432/ragdb |
3. Defining the Entity and Repository
Let’s create a JPA entity that maps to our documents table. The PgVector type from the pgvector library handles the vector column.
1 | import com.pgvector.PGvector; |
Now, the repository. Spring Data JPA doesn’t natively support vector similarity search, so we’ll write a custom query using JPQL or native SQL. Here’s how:
1 | import com.pgvector.PGvector; |
The <=> operator is pgvector’s cosine distance operator. We also support inner product (<#>) and Euclidean distance (<->).
4. Embedding Service
We need a service that converts text into embeddings using an LLM provider. Here’s a simple implementation using OpenAI’s API:
1 | import com.theokanning.openai.embedding.Embedding; |
Important: Batch your embedding requests to reduce API calls and costs. For large document sets, consider using async processing.
5. Document Service
Now, let’s create a service that handles document ingestion and retrieval:
1 | import com.pgvector.PGvector; |
6. REST Controller
Expose endpoints for adding documents and querying them:
1 | import org.springframework.http.ResponseEntity; |
7. Integrating with an LLM for RAG
The search results alone are not the final answer. The true power of RAG comes from feeding these retrieved documents into an LLM to generate a coherent response. Let’s add a method to the DocumentService that does this:
1 | import com.theokanning.openai.completion.chat.*; |
Add this method to the controller:
1 |
|
8. Testing the Pipeline
Let’s test with some sample data. Start your PostgreSQL and Spring Boot app, then:
1 | # Add a document |
The /ask endpoint should return a natural language answer based on the stored content.
9. Performance Optimization and Best Practices
Indexing Strategy
- IVFFlat is faster to build and uses less memory, but requires tuning
listsparameter. A good starting point islists = sqrt(number_of_rows). - HNSW offers better query performance and doesn’t need tuning, but uses more memory.
For production, I recommend HNSW for datasets up to millions of vectors.
Chunking
Don’t store entire documents as one embedding. Large documents lose semantic meaning when compressed into a single vector. Instead, chunk your documents into smaller pieces (e.g., 500-1000 tokens) with some overlap. This improves retrieval accuracy significantly.
Caching Embeddings
If you have a static knowledge base, cache the embeddings to avoid regenerating them. You can store the embedding in a separate column or use a hash of the content to check if it already exists.
Async Processing
For bulk ingestion, use @Async methods or a message queue like RabbitMQ to process embeddings in parallel and avoid blocking the main thread.
Security
- Never expose your OpenAI API key in client-side code. Use environment variables or secrets management.
- Validate user input to prevent prompt injection attacks.
10. Advanced: Hybrid Search
Pure vector search sometimes misses exact keyword matches. Consider adding full-text search (FTS) for hybrid retrieval. PostgreSQL supports both:
1 | ALTER TABLE documents ADD COLUMN tsv tsvector; |
Then combine both scores in your query. This approach is more robust for real-world applications.
Key Takeaways
- RAG is a game-changer for building domain-specific AI applications without fine-tuning.
- pgvector + Spring Boot provides a seamless way to add vector similarity search to your existing Java stack.
- Use the right index: HNSW for large datasets, IVFFlat for smaller ones.
- Chunk your documents to improve retrieval precision.
- Always cache embeddings to save API costs.
- Combine vector search with full-text search for hybrid retrieval that handles both semantic and exact matches.
- Security is critical: protect API keys and sanitize inputs.
Now you have a fully functional RAG pipeline. Experiment with different embedding models, chunk sizes, and index parameters to optimize for your specific use case. Happy coding!