A low-level, high-control RESTful API built with Java 25 for managing currency exchange rates and performing financial conversions. This project demonstrates manual infrastructure management, from database connection pooling and migrations to custom request body parsing and dependency injection.
The application follows a tiered architecture to ensure a clear separation of concerns. Below is the end-to-end data flow from the client to the database.
sequenceDiagram
participant Client
participant Servlet as Controller (Servlet)
participant Service as Service Layer
participant DAO as Data Access Object
participant DB as SQLite Database
Client->>Servlet: HTTP Request (GET, POST, PATCH)
Note over Servlet: Manual Parameter Parsing (HttpUtil)
Servlet->>Service: Business Logic Execution
Note over Service: Currency Conversion / Calculations
Service->>DAO: Data Retrieval/Persistence Request
DAO->>DB: SQL Execution via JDBC (HikariCP)
DB-->>DAO: ResultSet
Note over DAO: Manual Entity Mapping (RowMapper)
DAO-->>Service: Domain Entity
Service-->>Servlet: DTO (ExchangeValue) / Entity
Note over Servlet: Manual JSON Serialization (Jackson)
Servlet-->>Client: JSON Response + HTTP Status
Unlike high-level frameworks, this project manages the entire lifecycle of a request manually. This includes:
- Manual Request Parsing: Handling complex scenarios like the
PATCHmethod, where servlet containers do not automatically parsex-www-form-urlencodedbodies. - Explicit Mapping: Manually mapping database
ResultSetrows to Java Entities and then to DTOs for JSON output.
To avoid the "Singleton anti-pattern" and improve testability, the project implements a manual DI container via AppContext.
- Decoupling: Services receive their DAOs through constructors rather than static
getInstance()calls. - Centralized Wiring: All components are instantiated and wired in a single location.
An AppContextListener serves as the application's entry point, managing the global configuration.
- Automated Migrations: Upon initialization, the listener triggers
DatabaseManagerto run Flyway migrations, ensuring the SQLite schema is always up-to-date. - Resource Cleanup: It ensures the HikariCP connection pool is gracefully closed during application shutdown.
A centralized ExceptionFilter manages application-wide errors, ensuring consistent API responses.
- Exception Mapping: Custom business exceptions (e.g.,
EntityNotFoundException,DataIntegrityViolationException) are mapped to precise HTTP status codes like404 Not Found,409 Conflict, or400 Bad Request.
The project utilizes HikariCP for high-performance connection pooling with SQLite.
- SQLite Constraints: Mandatory configuration of
PRAGMA foreign_keys = ON;is enforced at the connection level to maintain relational integrity.
- Java 25 JDK
- Maven 3.9+
- Servlet Container (e.g., Apache Tomcat 10+)
The application requires an environment variable to locate the database file:
- DB_DIR: Set this to the directory where the SQLite database file should be stored (e.g.,
/var/lib/app/data).
- Clone the repository.
- Navigate to the project root and run:
mvn clean package
This will generate a currency-exchange-web.war file in the currency-exchange-web/target/ directory.
- Copy the generated
.warfile to your servlet container's deployment directory (e.g.,webapps/for Tomcat). - Start the container.
- The application will automatically perform database migrations and seed initial data upon startup.
- Java 25 (Core language)
- Persistence: SQLite, JDBC, HikariCP, Flyway
- Web: Jakarta Servlet API, Jackson Databind
- Utilities: Lombok