A Spring Boot POC for a shipping line backend. Internal operations teams can create voyages between ports and book containers (freight orders) onto those voyages.
Port ←── Voyage ──→ Port
│
│ Vessel
│
FreightOrder
│
Container (20ft / 40ft, DRY / REEFER / …)
Key entities:
- Port – identified by UN/LOCODE (e.g.
AEJEAfor Jebel Ali) - Vessel – identified by 7-digit IMO number
- Container – ISO 6346 code, size (20/40 foot), type (DRY, REEFER, OPEN_TOP, FLAT_RACK, TANK)
- Voyage – a scheduled vessel trip from departure port → arrival port
- FreightOrder – books a container onto a voyage, placed by an internal team member
| Tool | Version |
|---|---|
| Java (JDK) | 21+ |
| Maven | 3.8+ |
| Docker | 20+ |
| Docker Compose | 2+ |
cd docker
docker compose up -dThis creates a PostgreSQL 16 instance at localhost:5432 with database freightops and credentials freight/freight.
# From the project root
./mvnw clean install
# Run the app
./mvnw spring-boot:runThe server starts on http://localhost:8080. On first boot, Hibernate creates the tables and data.sql seeds sample ports, a vessel, and a few containers.
Create a voyage first (there's no controller for this yet — that's your job!), or insert one directly:
-- Connect to postgres: docker exec -it freightops-db psql -U freight -d freightops
INSERT INTO voyages (voyage_number, vessel_id, departure_port_id, arrival_port_id,
departure_time, arrival_time, status, created_at, updated_at)
VALUES ('VOY-2025-001', 1, 1, 2, '2025-04-01 08:00', '2025-04-15 18:00', 'PLANNED', NOW(), NOW());Create a freight order:
curl -X POST http://localhost:8080/api/v1/freight-orders \
-H 'Content-Type: application/json' \
-d '{
"voyageId": 1,
"containerId": 1,
"orderedBy": "ops-team",
"notes": "Fragile cargo"
}'List all freight orders:
curl http://localhost:8080/api/v1/freight-ordersGet a single order:
curl http://localhost:8080/api/v1/freight-orders/1Tests use an H2 in-memory database — no PostgreSQL needed.
./mvnw testLook at FreightOrderControllerTest.java for a working example of how to write integration tests with MockMvc and JUnit 5 (Jupiter).
src/main/java/com/shipping/freightops/
├── FreightOpsApplication.java # Entry point
├── config/
│ └── GlobalExceptionHandler.java # Centralized error handling
├── controller/
│ └── FreightOrderController.java # ★ Sample controller — follow this pattern
├── dto/
│ ├── CreateFreightOrderRequest.java
│ └── FreightOrderResponse.java
├── entity/
│ ├── BaseEntity.java # Shared id + audit fields
│ ├── Container.java
│ ├── FreightOrder.java
│ ├── Port.java
│ ├── Vessel.java
│ └── Voyage.java
├── enums/
│ ├── ContainerSize.java
│ ├── ContainerType.java
│ ├── OrderStatus.java
│ └── VoyageStatus.java
├── repository/
│ ├── ContainerRepository.java
│ ├── FreightOrderRepository.java
│ ├── PortRepository.java
│ ├── VesselRepository.java
│ └── VoyageRepository.java
└── service/
└── FreightOrderService.java
This POC has one working controller (FreightOrderController). Your tasks:
- VoyageController – CRUD for voyages (create a voyage between two ports on a vessel)
- ContainerController – CRUD for containers
- PortController – CRUD for ports
- VesselController – CRUD for vessels
For each controller, follow the same pattern:
- Create a Request DTO (e.g.
CreateVoyageRequest) with validation annotations - Create a Response DTO (e.g.
VoyageResponse) with afromEntity()factory method - Create a Service class with business logic
- Create a Controller with REST endpoints
- Write a test class following
FreightOrderControllerTestas a template
This project uses Google Java Format. The Maven build auto-formats on compile via the fmt-maven-plugin.
To manually format:
./mvnw fmt:formatTo check formatting without changing files:
./mvnw fmt:checkIDE setup:
- IntelliJ: Install the "google-java-format" plugin → Settings → google-java-format → Enable
- VS Code: Use the "Google Java Format" extension
| Command | Description |
|---|---|
./mvnw clean install |
Build + run tests |
./mvnw spring-boot:run |
Start the app |
./mvnw test |
Run tests only (H2, no Docker) |
./mvnw fmt:format |
Format code (Google style) |
docker compose -f docker/docker-compose.yml up -d |
Start PostgreSQL |
docker compose -f docker/docker-compose.yml down -v |
Stop + delete data |
Ready to pick up a task? See CONTRIBUTING.md for workflow, branch naming, and PR guidelines.
- Phase 1 — Core CRUD and foundations: ISSUES.md
- Phase 2 — Pricing, invoicing, vessel planning, finance, and commissions: ISSUES-PHASE2.md
All issues use a domain-prefixed naming convention (e.g. PRC-001, VPL-002) so dependencies are
easy to follow. See the naming table at the top of each issues file.
- Don't skip the DTO layer — never expose JPA entities directly in REST responses.
- Use
@Transactional(readOnly = true)on read-only service methods for better performance. - Fetch type is LAZY on all
@ManyToOnerelations — be mindful ofLazyInitializationExceptionif you access relations outside a transaction. - Validation is handled via Jakarta annotations (
@NotNull,@NotBlank, etc.) — theGlobalExceptionHandlerconverts these into clean 400 responses automatically. - Check existing repositories for query method naming conventions before writing custom
@Query.