A document question-answering service built with Spring Boot 3 and Spring AI, using retrieval-augmented generation (RAG): upload documents, ask questions in natural language, and get answers grounded in your own content — with sources.
Status: core pipeline complete. RAG answer endpoint working end-to-end with local dev profile.
- Production-style RAG architecture with Spring AI (retrieval, grounding, source citations)
- Decoupled, async processing using Kafka for document ingestion
- Vector similarity search with pgvector, including index tuning (HNSW)
- Integration testing against real infrastructure with Testcontainers
- End-to-end ownership: architecture, implementation, testing, and CI
┌──────────────┐ ┌─────────────────┐
upload ──────► │ REST API │ ─────► │ Kafka topic │
│ (Spring Boot)│ │ doc-ingestion │
└──────┬───────┘ └────────┬─────────┘
│ │ async
question ────────────►│ ┌────────▼─────────┐
│ │ Ingestion worker │
┌──────▼───────┐ │ chunk + embed │
│ RAG pipeline │ └────────┬─────────┘
│ retrieve + │ │
│ generate │ ┌────────▼─────────┐
└──────┬───────┘ ◄──────│ PostgreSQL │
│ similarity │ + pgvector (HNSW) │
answer + sources ◄────┘ search └──────────────────┘
- Ingestion — documents are uploaded via REST and published to a Kafka topic; an async
consumer chunks the text, creates embeddings (OpenAI
text-embedding-3-small), and stores them in PostgreSQL with the pgvector extension (HNSW index, cosine distance). - Retrieval — a question is embedded and the most similar chunks are fetched via vector similarity search.
- Generation — the retrieved chunks are passed as context to the chat model
(
gpt-4o-mini), which answers strictly from the provided sources and cites them.
| Area | Technology |
|---|---|
| Framework | Java 17, Spring Boot 3.4, Spring AI 1.0 |
| Vector store | PostgreSQL 16 + pgvector |
| Messaging | Apache Kafka (Zookeeper-based, Confluent images) |
| AI models | OpenAI chat + embeddings via Spring AI |
| Testing | JUnit 5, Mockito, Testcontainers (PostgreSQL, Kafka) |
| Build / CI | Maven, GitHub Actions, Docker Compose |
# 1. start infrastructure (Postgres + pgvector, Zookeeper, Kafka)
docker compose up -d
# 2a. with a real OpenAI key (full semantic search + RAG)
export OPENAI_API_KEY=sk-...
./mvnw spring-boot:run
# 2b. without an OpenAI key (local dev mode — fake embeddings, no API cost)
SPRING_PROFILES_ACTIVE=local ./mvnw spring-boot:runHealth check: curl localhost:8080/actuator/health
curl -X POST -F "[email protected]" localhost:8080/api/documents
# returns: { "id": "...", "filename": "...", "status": "UPLOADED", "extractedChars": N }curl -X POST -H "Content-Type: application/json" \
-d '{"question":"What is Kafka?"}' \
localhost:8080/api/search
# returns: [{ "content": "...", "documentId": "...", "score": 0.74 }]curl -X POST -H "Content-Type: application/json" \
-d '{"question":"What is Kafka?"}' \
localhost:8080/api/ask
# returns: { "answer": "...", "sources": [{ "content": "...", "documentId": "...", "score": 0.74 }] }# Status flow: UPLOADED → PROCESSING → INDEXED (or FAILED)
docker exec -it smart-document-qa-postgres-1 psql -U docqa -d docqa \
-c "SELECT id, filename, status FROM documents ORDER BY created_at DESC LIMIT 5;"- Project skeleton: Spring Boot 3, Docker Compose (pgvector, Kafka)
- Document upload endpoint with Apache Tika text extraction
- Kafka producer: publish document ID on upload
- Kafka consumer: chunking + embeddings + pgvector storage
- Vector similarity search over pgvector
- RAG answer endpoint with source citations
- Local dev profile: fake embedding model (no OpenAI cost)
- Integration tests with Testcontainers
- GitHub Actions CI (build + test on every push)
Built to go deeper into production-style GenAI architecture on the Java stack: async ingestion with Kafka, vector search tuning in pgvector, and grounded, citable LLM answers — the same patterns behind enterprise RAG systems.