ADVANCE-RAG is a local, configurable Retrieval-Augmented Generation (RAG) service built with Java 21, Spring Boot, Spring AI, Ollama, Qdrant, and Kafka. It ingests PDF documents asynchronously, extracts and chunks their contents, indexes searchable representations, and answers questions using only retrieved context.
The project is designed as an understandable reference implementation: each major RAG stage has its own package and can be configured independently. The default local runtime is Ollama for generation and embeddings, Qdrant for vector search, an in-memory BM25 index for lexical search, and Kafka for asynchronous ingestion.
- What the application does
- Architecture and request flow
- Project structure
- Prerequisites
- Run on any supported system
- Configuration
- API reference
- Chunking strategies
- Retrieval quality features
- Provider options
- Operations, verification, and troubleshooting
- Current implementation notes
- Accepts a PDF and optional document metadata through a REST endpoint.
- Saves the upload locally and sends a small ingestion event to Kafka.
- A Kafka consumer extracts PDF pages with Apache Tika.
- The configured chunking strategy splits pages into searchable chunks.
- The service creates chunk vectors plus title, summary, and keyword representations.
- Qdrant stores dense vectors; an in-memory BM25 index stores lexical terms.
- For a question, the service applies query transformations, filters, hybrid retrieval, optional multi-hop retrieval, parent-child expansion, reranking, and context compression.
- Ollama receives the compact retrieved context and returns a grounded answer with citations.
PDF upload
-> local staging area
-> Kafka ingestion event
-> Tika PDF extraction
-> metadata enrichment
-> configured chunking strategy
-> child chunks + multi-vector representations
-> Qdrant dense index + in-memory BM25 index
Question + optional filters
-> query transformation
-> metadata/access filtering
-> Qdrant semantic search + BM25 keyword search
-> RRF or weighted rank fusion
-> optional multi-hop search
-> parent-document expansion
-> reranking
-> context compression
-> Ollama answer generation
-> answer + citations
The detailed editable flow diagram is available in the docs folder.
ADVANCE-RAG/
├── compose.yaml # Qdrant, Kafka, and optional provider containers
├── pom.xml # Maven dependencies and Java 21 build configuration
├── README.md # This guide
├── docs/
│ └── <flow-diagram>.drawio # Editable end-to-end flow diagram
├── data/
│ └── uploads/ # Created at runtime; staged PDF uploads
└── src/
└── main/
├── java/com/advance/rag/
│ ├── AdvanceRagApplication.java # Spring Boot entry point; enables Kafka
│ ├── api/controller/ # REST endpoints
│ │ ├── RagController.java # PDF ingestion, job status, and question APIs
│ │ └── ProviderController.java # Active provider selection API
│ ├── chunking/ # Configurable document splitting strategies
│ │ ├── config/ # Chunking settings model
│ │ ├── core/ # Strategy contract and shared utilities
│ │ ├── service/ # Strategy selection service
│ │ └── strategy/
│ │ ├── basic/ # Default, fixed-size, sliding-window
│ │ ├── structural/ # Recursive, paragraph, sentence, hierarchy
│ │ └── contentaware/ # Semantic, Markdown, table, code aware
│ ├── config/ # Provider selection configuration model
│ ├── exception/ # Consistent REST error handling
│ ├── ingestion/ # Kafka-backed asynchronous PDF ingestion
│ ├── model/ # API, ingestion, metadata, and retrieval records
│ ├── observability/ # Micrometer RAG metrics
│ ├── provider/ # Provider gateway and connection definitions
│ │ ├── gateway/
│ │ ├── llm/ # Ollama, LocalAI, vLLM definitions
│ │ └── vector/ # Qdrant, Chroma, Milvus definitions
│ ├── rag/ # End-to-end RAG orchestration service
│ └── retrieval/ # Retrieval quality pipeline
│ ├── compression/ # Query-aware context compression
│ ├── filter/ # Metadata/access filter translation
│ ├── hybrid/ # Dense + BM25 fusion
│ ├── lexical/ # In-memory BM25 index
│ ├── parentchild/ # Parent expansion and multi-vector indexing
│ ├── rerank/ # Candidate reordering strategies
│ └── transform/ # Query transformation and multi-hop planning
└── resources/
└── application.yml # Defaults and environment-variable overrides
| Dependency | Required version | Purpose |
|---|---|---|
| Java Development Kit | 21 | Builds and runs the Spring Boot application |
| Maven | 3.9+ | Resolves dependencies, tests, packaging |
| Docker Engine / Docker Desktop | Current version with Compose v2 | Runs Qdrant and Kafka locally |
| Ollama | Current version | Runs the default chat and embedding models |
| Available disk memory | At least 8 GB recommended | Stores Docker images, models, vectors, and PDFs |
The application has been configured for Java 21. Java 17 or older is not supported by the Maven configuration.
- macOS: install a JDK such as Temurin 21, Maven, Docker Desktop, and Ollama.
- Windows: install JDK 21, Maven, Docker Desktop with the WSL 2 backend enabled, and Ollama for Windows. Use PowerShell, Windows Terminal, Git Bash, or WSL for the commands below.
- Linux: install OpenJDK 21, Maven, Docker Engine plus Docker Compose v2, and Ollama. Ensure your user can run
dockerwithoutsudo, or usesudoconsistently.
Confirm installations before proceeding:
java -version
mvn -version
docker --version
docker compose version
ollama --versionIf docker: command not found appears, Docker Desktop/Engine is not installed or is not available in the current terminal session. Install it, start the Docker application/service, then open a new terminal.
Clone the repository or copy the complete ADVANCE-RAG directory to the target machine. Then open a terminal in the project root:
cd /path/to/ADVANCE-RAGWindows PowerShell example:
cd C:\path\to\ADVANCE-RAGThe default Compose stack starts Qdrant and Kafka:
docker compose up -d
docker compose psExpected services and host ports:
| Service | Host port | Purpose |
|---|---|---|
| Qdrant HTTP | 6333 |
Qdrant dashboard/API |
| Qdrant gRPC | 6334 |
Spring AI vector-store connection |
| Kafka | 9092 |
Asynchronous ingestion events |
You can inspect container output if startup fails:
docker compose logs qdrant
docker compose logs kafkaStart Ollama if your operating system does not start it automatically, then download the configured chat and embedding models:
ollama pull llama3.2
ollama pull nomic-embed-textThe defaults can be changed with environment variables described in Configuration. The embedding model must remain available while the application is running, because it is used at ingestion and query time.
mvn clean testThis downloads Maven dependencies on the first run. A successful test/build is the best first check that JDK 21 and Maven are configured correctly.
For development:
mvn spring-boot:runFor a packaged JAR:
mvn clean package
java -jar target/advance-rag-0.0.1-SNAPSHOT.jarThe HTTP service listens on http://localhost:8080 by default.
curl http://localhost:8080/actuator/health
curl http://localhost:8080/api/providers/activeExpected provider response with default configuration:
{
"vectorDatabase": "qdrant",
"llm": "ollama"
}Stop containers while retaining Qdrant and Kafka data:
docker compose downTo delete container volumes and all indexed local infrastructure data, use the following destructive command only when you intentionally want a clean environment:
docker compose down -vThe locally staged PDFs are stored under data/uploads by default and are not removed by docker compose down.
All default settings are in src/main/resources/application.yml. Spring Boot permits values to be overridden with environment variables, command-line flags, or a deployment-specific configuration file.
| Configuration property | Environment variable | Default | Description |
|---|---|---|---|
spring.ai.ollama.base-url |
OLLAMA_BASE_URL |
http://localhost:11434 |
Ollama server URL |
spring.ai.ollama.chat.options.model |
OLLAMA_CHAT_MODEL |
llama3.2 |
Answering, transformation, and LLM-reranking model |
spring.ai.ollama.embedding.options.model |
OLLAMA_EMBEDDING_MODEL |
nomic-embed-text |
Dense embedding model |
spring.ai.vectorstore.qdrant.host |
QDRANT_HOST |
localhost |
Qdrant host |
spring.ai.vectorstore.qdrant.port |
QDRANT_PORT |
6334 |
Qdrant gRPC port |
spring.kafka.bootstrap-servers |
KAFKA_BOOTSTRAP_SERVERS |
localhost:9092 |
Kafka broker |
rag.ingestion.storage-path |
RAG_INGESTION_STORAGE_PATH |
./data/uploads |
Local PDF staging directory |
Example for a remote Ollama and Qdrant installation:
export OLLAMA_BASE_URL=http://ollama.internal:11434
export QDRANT_HOST=qdrant.internal
export QDRANT_PORT=6334
mvn spring-boot:runPowerShell equivalent:
$env:OLLAMA_BASE_URL = "http://ollama.internal:11434"
$env:QDRANT_HOST = "qdrant.internal"
$env:QDRANT_PORT = "6334"
mvn spring-boot:runMost RAG strategies are JSON values to make a single configuration value portable across YAML, Docker, Kubernetes, and environment variables. Update application.yml for a shared default, or override the relevant value at deployment time.
rag:
chunking:
config: '{"strategy":"recursive","chunkSize":800,"chunkOverlap":120,"sentenceWindow":2,"semanticSimilarityThreshold":0.72,"tableRowsPerChunk":40}'
hybrid-search:
config: '{"fusion":"rrf","candidateCount":8,"rrfK":60,"denseWeight":0.6,"keywordWeight":0.4}'
query-transformation:
config: '{"enabled":true,"techniques":["rewrite","expansion","multi-query"],"maxQueries":6}'
reranking:
config: '{"strategy":"hybrid","topK":5,"biEncoderWeight":0.35,"keywordWeight":0.25,"crossEncoderWeight":0.40}'
context-compression:
config: '{"strategy":"hybrid","maxSentencesPerDocument":4,"maxCharacters":6000}'
multi-hop:
config: '{"enabled":true,"maxHops":1}'These settings are read at request time; restart the application after changing a YAML file to ensure Spring loads the new file.
All APIs are rooted at http://localhost:8080.
POST /api/rag/documents
Content type: multipart/form-data
| Field | Required | Description |
|---|---|---|
file |
Yes | A non-empty .pdf file; maximum upload size is 25 MB by default |
metadata |
No | JSON string describing filtering, ownership, and citation data |
Example:
curl -X POST http://localhost:8080/api/rag/documents \
-F "file=@/absolute/path/to/travel-policy.pdf" \
-F 'metadata={"title":"India Travel Policy","department":"Finance","country":"India","publishedDate":"2025-04-01","source":"SharePoint","documentType":"Policy","owner":"finance-team","confidentiality":"internal","visibility":"internal","tags":["policy","travel"],"allowedUsers":["finance-analyst"]}'Successful submission returns 202 Accepted:
{
"job": {
"jobId": "7d2c...",
"documentId": "a SHA-256 hash",
"status": "QUEUED",
"chunks": null,
"error": null
}
}GET /api/rag/documents/jobs/{jobId}
curl http://localhost:8080/api/rag/documents/jobs/<jobId>Statuses are QUEUED, PROCESSING, COMPLETED, or FAILED. COMPLETED includes the number of indexed chunk and multi-vector representations.
POST /api/rag/query
curl -X POST http://localhost:8080/api/rag/query \
-H "Content-Type: application/json" \
-d '{
"question":"What does the travel policy say about eligible expenses?",
"filters":{
"department":"Finance",
"country":"India",
"publishedAfter":"2025-01-01",
"tags":["policy"],
"userId":"finance-analyst"
}
}'Supported filter properties are department, country, source, documentType, publishedAfter, publishedBefore, tags, and userId. Omit filters to search publicly visible documents. The response contains answer and citation entries with title, source, page number where available, and parent-section ID.
GET /api/providers/active
curl http://localhost:8080/api/providers/activecurl http://localhost:8080/actuator/health
curl http://localhost:8080/actuator/metricsSpring Boot Actuator exposes health, info, and metrics only. The RAG query path records query timing and failures through Micrometer.
Set rag.chunking.config.strategy to one of the following values.
| Strategy | Best suited for | Behaviour |
|---|---|---|
default |
General PDFs | Delegates to recursive chunking |
fixed-size |
Predictable-size text | Character windows with overlap |
sliding-window |
Boundary-sensitive QA | Overlapping fixed windows |
recursive |
General prose and mixed PDF text | Splits on paragraphs, lines, sentences, then words |
paragraph |
Well-structured prose | Keeps paragraphs together where possible |
sentence-window |
Narrative or FAQ content | Groups overlapping sentences |
hierarchical |
Documents where broad context matters | Produces linked parent/child structure |
semantic |
Topic-changing prose | Uses sentence embeddings and a similarity threshold |
markdown-aware |
Markdown-like documentation | Preserves headings with their sections |
table-aware |
Tabular extraction | Retains table blocks and repeats headers across row groups |
code-aware |
Source-code-like documentation | Groups common declarations; not a language AST parser |
Example semantic configuration:
rag:
chunking:
config: '{"strategy":"semantic","chunkSize":800,"chunkOverlap":120,"sentenceWindow":2,"semanticSimilarityThreshold":0.72,"tableRowsPerChunk":40}'Semantic chunking requires the Ollama embedding model and will make ingestion slower than deterministic strategies.
Metadata is copied from the upload to every parent, chunk, and multi-vector representation. Qdrant and BM25 both apply the same department, country, source, type, date, tag, and visibility constraints before results are fused. A document is public when visibility is public; non-public documents require a matching userId in allowedUsers.
The supplied userId is a request attribute, not an authenticated identity. Put this service behind authentication and derive the identity/permissions on the server before using it for production access control.
Each indexed representation goes to Qdrant for semantic retrieval and BM25 for exact lexical retrieval. The results are merged with either:
rrf: Reciprocal Rank Fusion; recommended default and robust when score scales differ.weighted: linearly weighted rank contributions usingdenseWeightandkeywordWeight.
The BM25 index is in memory. Re-ingest documents after an application restart, or replace it with a durable lexical search system for production.
Supported techniques are rewrite, expansion, multi-query, hyde, step-back, and decomposition. Each uses the configured chat model. More techniques can improve recall but also add local-model calls and latency. Start with rewrite and multi-query for a practical balance.
| Strategy | Behaviour | Cost |
|---|---|---|
bi-encoder |
Keeps hybrid retrieval order | Lowest |
bm25 / keyword |
Reorders by query term overlap | Low |
cross-encoder / llm-cross-encoder |
Uses the chat model to score each question-document pair | High |
hybrid |
Combines original rank, keyword score, and LLM score | Highest |
extract, summarize, and hybrid use the chat model. sentence-selection, compression-retriever, and token-pruning provide deterministic alternatives. maxCharacters is a final hard guard on the prompt context sent to the answering model.
PDF pages are retained as parent documents; searchable chunks point to a parentId. Matching children are expanded back to their parent before reranking, which restores surrounding context. Title, summary, and frequency-based keyword representations are indexed as additional retrieval routes. Multi-hop retrieval can create one follow-up question from first-hop evidence and merge its results.
The provider gateway validates configured provider names and reports the selected values:
rag:
providers:
config: '{"vectorDatabase":"qdrant","llm":"ollama"}'Recognised vector database names: qdrant, chroma, milvus.
Recognised LLM names: ollama, localai, vllm.
Optional provider containers can be started with:
docker compose --profile providers up -d| Optional service | Port | Notes |
|---|---|---|
| Chroma | 8000 |
Vector database container |
| Milvus | 19530 |
Starts with required etcd and MinIO dependencies |
| MinIO console | 9001 |
Milvus object-storage console; default credentials are in compose.yaml |
| LocalAI | 8080 |
Requires models to be installed/configured inside its mounted model directory |
| vLLM | 8001 |
Requires suitable GPU/container runtime in real deployments |
Important: the current live Spring AI retrieval and generation path is wired to Qdrant and Ollama. Chroma, Milvus, LocalAI, and vLLM connection classes and Docker services are available for provider configuration and health/selection groundwork, but they are not yet full runtime adapter replacements. Do not change the provider JSON expecting the active retrieval pipeline to automatically switch to those services without implementing their Spring AI adapters.
# Application logs when started through Maven
mvn spring-boot:run
# Container health and logs
docker compose ps
docker compose logs --tail=100 qdrant
docker compose logs --tail=100 kafka
# Confirm models installed in Ollama
ollama list
# Confirm Qdrant answers on its HTTP port
curl http://localhost:6333/collectionsInstall Docker Desktop on macOS/Windows or Docker Engine plus Compose v2 on Linux. Start it and open a new terminal. Confirm with docker compose version.
Ensure Ollama is running, then run ollama list and verify both configured model names exist. For a remote host, set OLLAMA_BASE_URL to a reachable URL. If Ollama runs in a container, localhost from the application container is not the host; use the appropriate Docker service hostname/network configuration.
Run docker compose ps, verify Qdrant is running, and use gRPC port 6334 for QDRANT_PORT. Port 6333 is the Qdrant HTTP/API port, not the configured Spring AI gRPC port.
Kafka may not have started or the application cannot reach it. Check docker compose logs kafka, confirm KAFKA_BOOTSTRAP_SERVERS is correct, and review application logs. A FAILED job includes the captured processing error in its status response.
Qdrant persists its Docker volume, but the current BM25 and parent-document stores live only in application memory. Re-ingest PDFs after a service restart, or implement durable storage/rebuild logic before relying on restart continuity.
Either stop the conflicting service or start ADVANCE-RAG on another port:
mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=8081"This project is a local/reference RAG application, not a hardened production deployment. Before exposing it beyond a trusted environment:
- Add authentication and authorization; do not trust caller-supplied
userIdvalues. - Store ingestion jobs, parent documents, and lexical indexes in durable shared storage.
- Place Qdrant, Kafka, and model endpoints on private networks and require authentication/TLS.
- Validate PDF type/content more deeply, scan uploads, and impose per-user quotas.
- Replace default MinIO credentials and avoid committing secrets to source control.
- Add Kafka retry topics, dead-letter handling, idempotent persistence, and distributed locking.
- Add request rate limits, audit logging, tracing, backups, retention rules, and alerting.
- Benchmark chunking, retrieval, reranking, and prompts using a labelled evaluation set before choosing defaults.
# Compile without running tests
mvn compile
# Run tests
mvn test
# Build runnable JAR
mvn clean package
# Start only default infrastructure
docker compose up -d
# Start default and optional provider containers
docker compose --profile providers up -d
# Stop containers, retain volumes
docker compose downNo license file is currently included. Add an explicit license before distributing or publishing the project.