A fully functional RESTful API built using pure Java with no external frameworks. This project demonstrates a deep understanding of how HTTP works under the hood by manually handling routing, request parsing, JSON serialization, and response formatting โ everything Spring Boot does automatically.
Most Java developers reach for Spring Boot without understanding what happens beneath it. This project was built to demonstrate:
- How HTTP servers work at the socket level
- Manual request routing and path variable extraction
- Thread-safe data handling under concurrent requests
- Clean layered architecture without framework scaffolding
| Tool | Purpose |
|---|---|
| Java 11+ | Core language |
com.sun.net.httpserver |
Built-in JDK HTTP server |
| Gson | JSON parsing and serialization |
| Maven | Build management |
| JUnit 5 | Unit testing |
mini-http-REST-api/
โโโ src/
โ โโโ main/java/
โ โ โโโ Server.java # Entry point โ starts the server
โ โ โโโ handler/
โ โ โ โโโ TaskHandler.java # Routes requests, sends responses
โ โ โโโ service/
โ โ โ โโโ TaskService.java # Business logic and validation
โ โ โโโ repository/
โ โ โ โโโ TaskRepository.java # In-memory data storage
โ โ โโโ model/
โ โ โโโ Task.java # Task model and Status enum
โ โโโ test/java/
โ โโโ TaskServiceTest.java # Unit tests for service layer
โโโ pom.xml
โโโ README.md
HTTP Request
โ
โผ
TaskHandler โ Parses request, routes to correct method, sends JSON response
โ
โผ
TaskService โ Validates input, applies business rules, throws errors
โ
โผ
TaskRepository โ Reads/writes to ConcurrentHashMap (in-memory store)
โ
โผ
Task โ Plain Java object (POJO) representing a task
- Java 11 or higher
- Maven 3.6+
# Clone the repository
git clone https://github.com/your-username/mini-http-api.git
cd mini-http-api
# Build the project
mvn clean install
# Run the server
mvn exec:java -Dexec.mainClass="Server"The server will start on:
http://localhost:8080
GET /tasks
Response 200 OK:
[
{
"id": 1,
"title": "Buy milk",
"description": "From the supermarket",
"status": "PENDING",
"createdAt": "2024-01-15T10:30:00"
}
]GET /tasks/{id}
Response 200 OK:
{
"id": 1,
"title": "Buy milk",
"description": "From the supermarket",
"status": "PENDING",
"createdAt": "2024-01-15T10:30:00"
}Response 404 Not Found:
{
"error": "Task with id 99 not found"
}POST /tasks
Content-Type: application/json
Request Body:
{
"title": "Buy milk",
"description": "From the supermarket"
}Response 201 Created:
{
"id": 1,
"title": "Buy milk",
"description": "From the supermarket",
"status": "PENDING",
"createdAt": "2024-01-15T10:30:00"
}Response 400 Bad Request (missing title):
{
"error": "Title is required"
}PUT /tasks/{id}
Content-Type: application/json
Request Body (all fields optional โ only send what you want to change):
{
"title": "Buy oat milk",
"status": "IN_PROGRESS"
}Valid status values: PENDING, IN_PROGRESS, DONE
Response 200 OK:
{
"id": 1,
"title": "Buy oat milk",
"description": "From the supermarket",
"status": "IN_PROGRESS",
"createdAt": "2024-01-15T10:30:00"
}DELETE /tasks/{id}
Response 204 No Content โ task deleted successfully
Response 404 Not Found:
{
"error": "Task with id 1 not found"
}| Code | Meaning | When it's returned |
|---|---|---|
200 |
OK | Successful GET or PUT |
201 |
Created | Successful POST |
204 |
No Content | Successful DELETE |
400 |
Bad Request | Missing or invalid fields |
404 |
Not Found | Task ID does not exist |
405 |
Method Not Allowed | Unsupported HTTP method on a route |
500 |
Internal Server Error | Unexpected server-side error |
There is no framework handling routing. TaskHandler.java manually matches the request path using string comparison and regex:
if (path.equals("/tasks")) { ... }
else if (path.matches("/tasks/\\d+")) { ... }The HttpServer processes requests on multiple threads simultaneously. To prevent race conditions:
ConcurrentHashMapis used instead of a regularHashMapfor the data storeAtomicIntegeris used for ID generation instead of a plainintcounter
Gson serializes Java objects to JSON responses and deserializes incoming request bodies โ the only external dependency in the project.
mvn testTests cover:
- Creating a task successfully
- Creating a task with a missing title โ expects
IllegalArgumentException - Getting a task that does not exist โ expects
IllegalArgumentException - Deleting a task that does not exist โ expects
IllegalArgumentException - Updating a task successfully
Why no Spring Boot? Spring Boot is excellent for production, but it abstracts away HTTP fundamentals. This project was intentionally built without it to demonstrate understanding of what happens at the lower level.
Why in-memory storage?
The focus of this project is the HTTP and architecture layer. A ConcurrentHashMap keeps the data layer simple so attention stays on routing, request handling, and thread safety. A database (PostgreSQL + JDBC) could be swapped in by only changing TaskRepository.java.
Why constructor injection?
Each class receives its dependencies through the constructor rather than creating them internally. This makes unit testing straightforward โ you can pass a mock repository into TaskService without any framework.
- Persist data to a PostgreSQL database via JDBC
- Add query filtering:
GET /tasks?status=PENDING - Add pagination:
GET /tasks?page=1&size=5 - Add request logging (method, path, response time)
- Add API key authentication via request header