A full-stack AI-powered financial assistant built around a Java 21 / Spring Boot gateway and a React + TypeScript frontend. Upload financial documents (10-Ks, earnings reports, prospectuses) and get grounded answers powered by Claude with retrieval-augmented context — all served through a secure REST API.
The project demonstrates:
- Spring Boot 3.3 as a Backend-For-Frontend (BFF) — typed DTOs, validation, normalized error envelopes, request IDs, downstream-aware health checks, streaming multipart uploads via
WebClient. - React + Vite + TypeScript + Tailwind — a polished fintech UI with drag-drop ingestion, real-time indexing status, suggested prompts, and per-session document isolation.
- A clean three-tier separation between presentation, gateway, and the AI retrieval service it fronts.
┌──────────────┐ ┌──────────────────────────┐ ┌──────────────────┐
│ React UI │ ─▶│ Spring Boot Gateway │ ─▶│ RAG Service │
│ Vite + TS │ │ Java 21 · Maven · :8080 │ │ Voyage·Pinecone·│
│ :5173 │ │ WebClient · Validation │ │ Cohere · Claude │
└──────────────┘ │ Error envelope · CORS │ │ :8000 │
└──────────────────────────┘ └──────────────────┘
The gateway owns everything browser-facing: it validates uploads (size, extension), forwards multipart streams to the retrieval service, normalizes errors into a typed ErrorEnvelope (with request IDs for tracing), exposes a downstream-aware /api/v1/health endpoint, and handles CORS so the React app has a clean path in.
gateway/src/main/java/com/finsight/gateway/
├── GatewayApplication.java
├── config/
│ ├── AppProperties.java # Typed @ConfigurationProperties record
│ ├── CorsConfig.java # Locked-down /api/** CORS
│ └── WebClientConfig.java # Reactor WebClient → upstream RAG service
├── controller/
│ ├── ChatController.java # POST /api/v1/query
│ ├── IngestController.java # POST /api/v1/upload, GET /api/v1/status/{id}
│ └── HealthController.java # /api/v1/health (gateway + upstream)
├── service/
│ └── RagClient.java # WebClient wrapper, error mapping
├── dto/
│ ├── ChatRequest.java # @Valid @NotBlank, defaults user_id
│ ├── ChatResponse.java
│ ├── UploadResponse.java
│ ├── StatusResponse.java
│ └── ErrorEnvelope.java # code, message, status, timestamp, requestId
└── exception/
├── UpstreamException.java
└── GlobalExceptionHandler.java # @RestControllerAdvice
Key Spring Boot patterns on display:
- Typed configuration via
@ConfigurationPropertiesrecords with sane defaults. - Reactive
WebClientfor streaming uploads and 120s LLM timeouts without thread starvation. - Centralized error handling with
@RestControllerAdvicemapping validation errors, payload-too-large, and upstream failures to a consistent JSON envelope. - Bean validation (
jakarta.validation) on request bodies via@Valid. - Spring Boot Actuator for
/actuator/healthalongside the custom downstream-aware health endpoint.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/health |
Gateway health plus upstream reachability |
| POST | /api/v1/upload |
Multipart upload (file, user_id) — validated and forwarded |
| GET | /api/v1/status/{file_id} |
Ingestion status (processing / completed) |
| POST | /api/v1/query |
RAG query: { "query": "...", "userId": "..." } |
Errors come back as:
{
"code": "VALIDATION_FAILED",
"message": "query must not be blank",
"status": 400,
"timestamp": "2026-04-28T15:21:09.184Z",
"requestId": "8b3c…"
}Prerequisites: Java 21, Maven 3.9+, Node 20+, and a running RAG service on :8000 (the gateway proxies to whatever URL RAG_SERVICE_URL points at — your existing retrieval service).
-
Configure environment
cp .env.example .env # fill in API keys; defaults are fine for everything else -
Run the gateway (terminal 1)
cd gateway mvn spring-boot:run # gateway is up at http://localhost:8080
-
Run the frontend (terminal 2)
cd frontend npm install npm run dev # open http://localhost:5173
Vite proxies
/apitolocalhost:8080, so there's nothing to wire by hand. -
Run with Docker Compose (alternative)
docker-compose up --build
Everything is driven by .env (see .env.example). Gateway-relevant keys:
| Key | Default | Notes |
|---|---|---|
RAG_SERVICE_URL |
http://localhost:8000 |
Upstream RAG service the gateway proxies to |
UPLOAD_MAX_BYTES |
52428800 |
50 MB hard cap before forwarding |
CORS_ALLOWED_ORIGINS |
http://localhost:5173 |
Comma-separated origins |
VITE_API_BASE_URL |
http://localhost:8080 |
Where the React app calls |
AI-Financial-Assistant/
├── gateway/ Spring Boot 3.3 / Java 21 / Maven
├── frontend/ React 18 + Vite + TypeScript + Tailwind
├── app/ RAG retrieval service (Voyage / Pinecone / Cohere / Claude)
├── tests/ Multi-tenancy isolation test
├── evaluation/ Golden dataset
├── docker-compose.yaml
└── .env.example
MIT. See LICENSE.