A robust Spring Boot service designed for managing trade finance operations, including real-time currency conversion, transaction auditing, and automated notifications.
- Java 21: Required for Record types and modern Spring Boot 3.2+ features.
- Maven 3.9+: For dependency management and building.
- Package Alignment: Ensure
TradeFinanceApplication.javais located in thecom.db.assessmentpackage to avoid component scanning issues. - Compile and Build:
mvn clean install
- Run the Application:
The server will start on
mvn spring-boot:run
http://localhost:8080by default.
The application integrates Spring Boot Actuator to provide production-ready health checks. This allows you to verify that the application and its core services are initialized correctly.
Visit the health endpoint to see the current status of the application.
- Endpoint:
GET /actuator/health - Command:
curl http://localhost:8080/actuator/health
- Expected Response:
{ "status": "UP" }
Perform currency conversions between USD, EUR, SGD, and GBP. This service validates the input amount and checks for supported currency codes.
- Endpoint:
POST /api/v1/trade-finance/convert - Test Command:
curl -X POST http://localhost:8080/api/v1/trade-finance/convert \ -H "Content-Type: application/json" \ -d '{"amount": 500, "from": "EUR", "to": "USD"}'
This is the primary workflow test. It triggers a series of backend actions: Validation β Auto-Approval Logic β Auditing β Notification.
- Endpoint:
POST /api/v1/trade-finance/transaction - Test Command:
curl -X POST http://localhost:8080/api/v1/trade-finance/transaction \ -H "Content-Type: application/json" \ -d '{ "amount": 1250.0, "currency": "USD", "submittedBy": "user@db.com" }'
Verification Steps:
- Response Status: Check if you receive a
200 OK(Approved) or202 Accepted(Pending). - Application Logs: Check the console for an audit entry:
AUDIT: TRANSACTION_SUBMITTED | user@db.com | 1250.0 USD STATUS=... - Notification: If approved, look for the log notification:
[LOG-NOTIFY] to=user@db.com | Transaction Approved
Verify that actions were recorded in the system audit trail.
- Endpoint:
GET /api/v1/trade-finance/audit?limit=5 - Command:
curl http://localhost:8080/api/v1/trade-finance/audit?limit=5
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Trade Finance API β
β (Spring Boot 3.2.4) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββββββ βββββββββββββββββββββββββββ β
β β Controller ββββββββββΆβ GlobalExceptionHandler β β
β β β β (@ControllerAdvice) β β
β ββββββββββ¬ββββββββ βββββββββββββββββββββββββββ β
β β β
β β inject β
β βΌ β
β ββββββββββββββββββ βββββββββββββββββββββββββββ β
β β Services ββββββββββΆβ AppProperties β β
β β - Converter β β (@ConfigurationProps) β β
β β - Audit β βββββββββββββββββββββββββββ β
β β - Notificationβ β
β ββββββββββ¬ββββββββ β
β β β
β β wired by β
β βΌ β
β ββββββββββββββββββ β
β β AppConfig β β @Profile, @Conditional, @Bean β
β β (Bean Factory)β β
β ββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Component | Responsibility | Pattern |
|---|---|---|
AppProperties |
Binds application.yml to Java objects |
@ConfigurationProperties + @Validated |
AppConfig |
Wires beans per environment | @Profile, @Conditional, @Bean |
GlobalExceptionHandler |
Centralizes error responses | @ControllerAdvice + @ExceptionHandler |
TradeFinanceController |
Exposes REST endpoints | @RestController + dependency injection |
| Service Implementations | Business logic | Interface segregation + strategy pattern |
com.db.assessment.controller: REST controllers for API exposure.com.db.assessment.service: Core interfaces (AuditService,CurrencyConverter,NotificationService) and models (AuditEntry).com.db.assessment.service.impl: Business logic implementations including:MockCurrencyConverter(Dev) /LiveCurrencyConverter(Prod)DatabaseAuditService(Active) /NoOpAuditService(Disabled)LogNotificationService(Console) /EmailNotificationService(SMTP)
com.db.assessment.exception: Unified error handling returning structuredErrorResponseobjects.
The application behavior is driven by the active Spring Profile:
- dev: Uses mock data and console logging.
- prod: Uses live FX rates and email notification services.
http://localhost:8080/api/v1/trade-finance
| Method | Path | Description |
|---|---|---|
GET |
/config |
Returns active configuration |
POST |
/convert |
Currency conversion |
GET |
/rates |
Current FX rates (cached) |
GET |
/audit?limit=10 |
Recent audit log |
POST |
/transaction |
Submit transaction for approval |
curl -X POST http://localhost:8080/api/v1/trade-finance/convert \
-H "Content-Type: application/json" \
-d '{
"amount": 1000,
"from": "USD",
"to": "EUR"
}'Response:
{
"from": "USD",
"to": "EUR",
"original": 1000.0,
"converted": 1087.0,
"timestamp": "2026-04-19T14:30:00Z"
}curl -X POST http://localhost:8080/api/v1/trade-finance/transaction \
-H "Content-Type: application/json" \
-d '{
"amount": 5000,
"currency": "USD",
"submittedBy": "trader1"
}'Response (dev β auto-approve):
{
"status": "APPROVED",
"amount": 5000.0,
"currency": "USD",
"submittedBy": "trader1",
"timestamp": "2026-04-19T14:30:00Z"
}curl -X POST http://localhost:8080/api/v1/trade-finance/convert \
-H "Content-Type: application/json" \
-d '{
"amount": -100,
"from": "USD",
"to": "EUR"
}'Response (400 Bad Request):
{
"status": 400,
"code": "INVALID_INPUT",
"message": "Amount below minimum: 1.0",
"timestamp": "2026-04-19T14:30:00Z"
} βββββββββββββββββββ
β 15 Integration β @SpringBootTest + MockMvc
β (Controller) β Full Spring context
βββββββββββββββββββ
βββββββββββββββββββββ
β 20 Context Tests β @SpringBootTest
β (Bean Wiring) β Profile verification
βββββββββββββββββββββ
βββββββββββββββββββββββββββββ
β 35+ Unit Tests β No Spring β FAST
β (Business Logic) β Pure Java
βββββββββββββββββββββββββββββ
mvn test# Unit tests (fast, no Spring)
mvn test -Dtest="TradeFinanceTests\$AppPropertiesUnitTests"
mvn test -Dtest="TradeFinanceTests\$TransactionValidatorTests"
# Integration tests (Spring context)
mvn test -Dtest="TradeFinanceTests\$ControllerTests"
mvn test -Dtest=ExceptionHandlerTests
# Profile-specific
mvn test -Dtest="TradeFinanceTests\$DevProfileContextTests"
mvn test -Dtest="TradeFinanceTests\$TestProfileContextTests"| Test Class | Tests | Coverage |
|---|---|---|
AppPropertiesUnitTests |
9 | Config binding, defaults |
TransactionValidatorTests |
10 | Business rules, edge cases |
FxRateCacheTests |
7 | Cache operations |
DevProfileContextTests |
6 | Bean wiring (dev profile) |
TestProfileContextTests |
3 | Bean wiring (test profile) |
ControllerTests |
12 | REST endpoints |
AppConfigBeanTests |
9 | Bean factory methods |
ExceptionHandlerTests |
9 | Error handling |
| Total | 65+ | All layers |
src/
βββ main/
β βββ java/com/db/assessment/
β β βββ AssessmentApplication.java # Main entry point
β β βββ config/
β β β βββ AppProperties.java # @ConfigurationProperties
β β β βββ AppConfig.java # Bean factory
β β βββ controller/
β β β βββ TradeFinanceController.java # REST endpoints
β β βββ exception/
β β β βββ GlobalExceptionHandler.java # @ControllerAdvice
β β βββ service/
β β βββ CurrencyConverter.java # Interface
β β βββ AuditService.java # Interface
β β βββ NotificationService.java # Interface
β β βββ impl/
β β βββ ServiceImplementations.java # 6 implementations
β βββ resources/
β βββ application.yml # Multi-profile config
βββ test/
βββ java/com/db/assessment/
βββ TradeFinanceTests.java # Main test suite
βββ ExceptionHandlerTests.java # Error handling tests
app:
trade-finance:
auto-approve: true # Auto-approve transactions
max-transaction-amount: 99999999.00
audit:
enabled: trueRun: mvn spring-boot:run
app:
trade-finance:
max-transaction-amount: 50000.00
audit:
enabled: false # NoOp audit
rate-limit:
enabled: false # No throttlingRun: mvn test
app:
trade-finance:
auto-approve: false # Manual approval
settlement-mode: "T+1"
security:
require-https: true
rate-limit:
requests-per-second: 50 # Stricter limitsRun: mvn spring-boot:run -Dspring-boot.run.profiles=prod
| Exception | HTTP Status | Error Code | Example |
|---|---|---|---|
IllegalArgumentException |
400 | INVALID_INPUT |
"Amount below minimum" |
MethodArgumentNotValidException |
400 | VALIDATION_FAILED |
@Valid failures |
HttpMessageNotReadableException |
400 | MALFORMED_JSON |
Invalid JSON |
MethodArgumentTypeMismatchException |
400 | TYPE_MISMATCH |
?limit=abc |
NoSuchElementException |
404 | RESOURCE_NOT_FOUND |
Not found |
UnsupportedCurrencyException |
400 | UNSUPPORTED_CURRENCY |
Custom exception |
InsufficientFundsException |
422 | INSUFFICIENT_FUNDS |
Custom exception |
Exception (catch-all) |
500 | INTERNAL_ERROR |
Unexpected errors |
{
"status": 400,
"code": "INVALID_INPUT",
"message": "Amount below minimum: 1.0",
"timestamp": "2026-04-19T14:30:00Z",
"details": {}
}