Spring Boot 3.4 + Vaadin Flow 24 + Java 21 fintech application. Server-side Java UI with Bridge (fintech API) and OpenAI integrations, MySQL persistence.
org.jhely.money.base/
├── ui/view/ # Vaadin @Route views (MainLayout shell)
│ └── payments/ # Finance views: AccountsOverviewView (main dashboard), Send/Receive
├── api/bridge/ # REST webhooks: BridgeWebhookController receives KYC/customer events
├── service/ # Business logic
│ ├── payments/ # BridgeOnboardingService, KycStatusBroadcaster
│ ├── BridgeApiClientFactory.java # ★ Factory for generated SDK clients
│ └── ExtractionWorker.java # Async AI receipt extraction
├── domain/ # JPA entities: BridgeCustomer, ReceiptFile, UserAccount
├── repository/ # Spring Data JPA repos
└── security/ # AuthenticatedUser (session-based via VaadinSession)
| Task | Command |
|---|---|
| Dev server (port 10000) | ./mvnw |
| Regenerate Bridge SDK | mvn generate-sources -Pgenerate-bridge-sdk |
| Format code | ./mvnw spotless:apply |
| Prod build | ./mvnw -Pproduction package |
| Integration tests | ./mvnw -Pintegration-test verify |
SDK Usage – Never write HTTP clients manually. Use BridgeApiClientFactory:
@Autowired BridgeApiClientFactory factory;
// Get typed APIs:
factory.customers().customersGet(...);
factory.webhooks().webhooksGet(...);- SDK source:
src/main/java/org/jhely/money/sdk/bridge/(committed to git, regenerate manually via Maven profile) - OpenAPI spec:
bridge-spec-official.json(root folder, git-ignored)
Onboarding Flow (AccountsOverviewView → BridgeOnboardingService):
- Check
BridgeCustomerexists viaonboarding.findForUser(userId, email) - If not, show onboarding card with "Initiate KYC" button
- On KYC completion,
BridgeWebhookControllerreceives webhook → updates entity → broadcasts viaKycStatusBroadcaster - UI live-updates via broadcaster listener pattern
Broadcaster Pattern for real-time UI updates:
// In view constructor:
kycBroadcaster.register(userId, updated -> ui.access(() -> refreshPanel(updated)));
// In detach listener:
card.addDetachListener(ev -> kycBroadcaster.unregister(userId, listener));- All protected views:
@RolesAllowed("USER")+@Route(value = "path", layout = MainLayout.class) - Finance views live in
ui/view/payments/withPaymentsSubnavcomponent - Use
ui.access(() -> ...)for async UI updates from background threads - Session user:
AuthenticatedUser.get()returnsOptional<UserAccount>
ReceiptServicehandles uploads with MIME validation via TikaExtractionWorker.process()is@Async– uploads files to OpenAI, extracts structured data- Virtual threads enabled (
spring.threads.virtual.enabled=true) – safe for blocking I/O
- Secrets: Use
secrets.properties(git-ignored) or env vars:${BRIDGE_SANDBOX_API_KEY},${OPENAI_API_KEY},${DB_URL} - Bridge mode:
bridge.mode=sandbox|liveswitches base URL and API key - Vaadin scanning: Restricted via
vaadin.allowed-packagesto avoid scanning generated SDK
Integration tests use @SpringBootTest + @ActiveProfiles("test"):
@SpringBootTest
@ActiveProfiles("test")
class CustomersSandboxIT { ... }See CustomersSandboxIT.java for Bridge API test patterns (idempotency keys, error handling).
- Never commit API keys – use env vars or
secrets.properties - All new code in
org.jhely.money.base.*package - Don't edit
src/main/frontend/generated/** - Bridge SDK in
src/main/java/org/jhely/money/sdk/bridge/is auto-generated – regenerate withmvn generate-sources -Pgenerate-bridge-sdkinstead of manual edits - Run
./mvnw spotless:applybefore committing