Skip to content

shendeyogesh11/Load-balancer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🚚 Dispatch Load Balancer

Java 17 Spring Boot 3.5.10 Maven 3.9.12 H2 Database JUnit 5

A high-performance Spring Boot application that solves the Vehicle Routing Problem (VRP) by optimally allocating delivery orders to a fleet of vehicles. It uses a custom Greedy Nearest-Neighbor Algorithm with the Haversine Formula to minimize total travel distance while strictly respecting priority tiers and vehicle capacity constraints.


πŸ“‘ Table of Contents


✨ Features

Feature Description
Priority-Based Scheduling Orders sorted HIGH β†’ MEDIUM β†’ LOW; within the same priority, heavier orders are assigned first
Capacity Enforcement Vehicle weight limits are strictly never exceeded during assignment
Greedy Nearest-Neighbor Each order is assigned to the closest eligible vehicle using the Haversine distance
Cumulative Routing After each assignment, a vehicle's position updates to the delivery location, simulating real multi-stop routing
Batch Upsert Operations Uses findAllByOrderIdIn / findAllByVehicleIdIn for a single bulk fetch then saveAll β€” zero N+1 queries
Graceful Overflow Handling Unassignable orders are collected by ID in unassignedOrders and returned in the response
H2 Web Console Built-in browser UI to inspect live DB state at /h2-console during development
SQL Logging spring.jpa.show-sql=true prints every Hibernate query to the console for easy debugging
Clean Architecture Controller β†’ Service β†’ Repository layers with strict DTO separation
Comprehensive Tests Unit tests (Mockito) + Integration tests (MockMvc) covering all critical paths

πŸ› οΈ Technology Stack

Layer Technology Version
Language Java 17
Framework Spring Boot (Web, Data JPA, Validation) 3.5.10
Database H2 In-Memory Runtime
Build Tool Maven Wrapper 3.9.12
Utilities Lombok Managed by Spring Boot BOM
Testing JUnit 5, Mockito, MockMvc Managed by Spring Boot BOM

πŸ“‚ Project Structure

shendeyogesh11-load-balancer/
β”œβ”€β”€ pom.xml
β”œβ”€β”€ mvnw / mvnw.cmd                               # Maven wrapper scripts
β”œβ”€β”€ .mvn/wrapper/maven-wrapper.properties
└── src/
    β”œβ”€β”€ main/
    β”‚   β”œβ”€β”€ java/com/dispatch/loadbalancer/
    β”‚   β”‚   β”œβ”€β”€ DispatchLoadBalancerApplication.java   # Spring Boot entry point
    β”‚   β”‚   β”œβ”€β”€ controller/
    β”‚   β”‚   β”‚   └── DispatchController.java            # REST endpoints
    β”‚   β”‚   β”œβ”€β”€ service/
    β”‚   β”‚   β”‚   └── DispatchService.java               # Batch upsert + optimization logic
    β”‚   β”‚   β”œβ”€β”€ repository/
    β”‚   β”‚   β”‚   β”œβ”€β”€ OrderRepository.java               # JPA + bulk finders
    β”‚   β”‚   β”‚   └── VehicleRepository.java             # JPA + bulk finders
    β”‚   β”‚   β”œβ”€β”€ model/
    β”‚   β”‚   β”‚   β”œβ”€β”€ Order.java                         # JPA Entity  (@Table name: "orders")
    β”‚   β”‚   β”‚   β”œβ”€β”€ Vehicle.java                       # JPA Entity  (@Table name: "vehicles")
    β”‚   β”‚   β”‚   └── Priority.java                      # Enum: HIGH, MEDIUM, LOW
    β”‚   β”‚   β”œβ”€β”€ dto/
    β”‚   β”‚   β”‚   └── DispatchPlanResponse.java          # Response DTO with nested static classes
    β”‚   β”‚   β”œβ”€β”€ util/
    β”‚   β”‚   β”‚   └── DistanceCalculator.java            # Haversine formula (@Component)
    β”‚   β”‚   └── exception/
    β”‚   β”‚       └── GlobalExceptionHandler.java        # @RestControllerAdvice
    β”‚   └── resources/
    β”‚       └── application.properties
    └── test/
        └── java/com/dispatch/loadbalancer/
            β”œβ”€β”€ DispatchLoadBalancerApplicationTests.java   # Context load smoke test
            β”œβ”€β”€ DispatchServiceTest.java                    # Unit tests (Mockito)
            β”œβ”€β”€ DispatchIntegrationTest.java                # End-to-end API tests (MockMvc)
            └── DistanceCalculatorTest.java                 # Additional unit test scenarios

🧠 How It Works

Step 1 β€” Data Ingestion (Batch Upsert)

When vehicles or orders are POST-ed, the service executes a two-query upsert that scales safely to large payloads:

  1. Fetch all existing records matching the incoming IDs in one query (findAllByOrderIdIn / findAllByVehicleIdIn).
  2. Build an O(1) lookup map from the results.
  3. Merge: update fields on existing entities, queue new ones for insert.
  4. Persist everything in one batch via saveAll.

Step 2 β€” Sorting

All orders are sorted before assignment using a two-level comparator:

Primary:   Priority rank      HIGH (1) β†’ MEDIUM (2) β†’ LOW (3)
Secondary: Package weight     DESCENDING  (heavier packages in the same tier go first)

The secondary sort ensures that bulkier items β€” which are harder to fit as capacity depletes β€” are placed early, maximising overall fleet utilisation.

Step 3 β€” Greedy Nearest-Neighbor Assignment

For each order (in sorted order), the algorithm runs inside DispatchService.generateDispatchPlan():

  1. Scan all vehicles that have sufficient remaining capacity for the order's weight.
  2. Among eligible vehicles, pick the one with the smallest Haversine distance from its current position to the order's coordinates.
  3. Assign the order β†’ add to assignedOrders, increment currentLoad, advance the vehicle's current position to the delivery coordinate, and accumulate totalDistanceKm.
  4. If no vehicle can carry the order, add its ID to unassignedOrders.

The position-update after each stop is handled by the private VehicleSimulationState inner class, keeping all mutable routing state separate from the immutable JPA entities.

Haversine Formula

Implemented in DistanceCalculator (Earth radius = 6,371 km):

Ξ”lat = latβ‚‚ βˆ’ lat₁  (radians)
Ξ”lon = lonβ‚‚ βˆ’ lon₁  (radians)

a = sinΒ²(Ξ”lat/2) + cos(lat₁) Β· cos(latβ‚‚) Β· sinΒ²(Ξ”lon/2)
c = 2 Β· atan2(√a, √(1βˆ’a))
d = 6371 Β· c  km

Distance appears in responses formatted as "%.2f km" (e.g., "5.30 km").


πŸš€ Getting Started

Prerequisites

  • JDK 17 or higher
  • No separate Maven installation needed β€” the bundled mvnw wrapper downloads Maven 3.9.12 automatically on first run.

Running on Windows

cd path\to\DispatchLoadBalancer

:: Using the included wrapper (recommended β€” no Maven required)
mvnw.cmd spring-boot:run

:: Or if Maven is installed globally
mvn spring-boot:run

Running on macOS / Linux

cd path/to/DispatchLoadBalancer

# Grant execute permission to the wrapper (first time only)
chmod +x mvnw

# Run with the wrapper (recommended β€” no Maven required)
./mvnw spring-boot:run

# Or if Maven is installed globally
mvn spring-boot:run

βœ… The server starts at http://localhost:8080


πŸ“‘ API Documentation

Base URL

http://localhost:8080/api/dispatch

Endpoints Overview

Method Endpoint Description
POST /vehicles Register or update the vehicle fleet
POST /orders Submit delivery orders for processing
GET /plan Trigger optimization and retrieve the dispatch plan

1. Upload Fleet Details

Registers or updates vehicles via batch upsert β€” re-submitting an existing vehicleId updates it in-place.

  • URL: POST /api/dispatch/vehicles
  • Content-Type: application/json

Request Body:

{
  "vehicles": [
    {
      "vehicleId": "VEH001",
      "capacity": 100,
      "currentLatitude": 28.7041,
      "currentLongitude": 77.1025,
      "currentAddress": "Karol Bagh, Delhi, India"
    },
    {
      "vehicleId": "VEH002",
      "capacity": 80,
      "currentLatitude": 28.5355,
      "currentLongitude": 77.3910,
      "currentAddress": "Sector 18, Noida, Uttar Pradesh, India"
    }
  ]
}

Response 200 OK:

{
  "message": "Vehicle details accepted.",
  "status": "success"
}

Response 400 Bad Request (empty or missing vehicles key):

{
  "message": "No vehicles provided"
}

2. Upload Delivery Orders

Submits orders via batch upsert. priority must be one of HIGH, MEDIUM, or LOW.

  • URL: POST /api/dispatch/orders
  • Content-Type: application/json

Request Body:

{
  "orders": [
    {
      "orderId": "ORD001",
      "latitude": 28.6139,
      "longitude": 77.2090,
      "address": "Connaught Place, Delhi, India",
      "packageWeight": 15,
      "priority": "HIGH"
    },
    {
      "orderId": "ORD002",
      "latitude": 28.4595,
      "longitude": 77.0266,
      "address": "Cyber Hub, Gurgaon, Haryana, India",
      "packageWeight": 30,
      "priority": "MEDIUM"
    }
  ]
}

Response 200 OK:

{
  "message": "Delivery orders accepted.",
  "status": "success"
}

Response 400 Bad Request (empty or missing orders key):

{
  "message": "No orders provided"
}

3. Generate Dispatch Plan

Runs the full optimization algorithm over all stored vehicles and pending orders.

  • URL: GET /api/dispatch/plan

Response 200 OK:

{
  "dispatchPlan": [
    {
      "vehicleId": "VEH001",
      "totalLoad": 45,
      "totalDistance": "5.30 km",
      "assignedOrders": [
        {
          "orderId": "ORD001",
          "latitude": 28.6139,
          "longitude": 77.2090,
          "address": "Connaught Place, Delhi, India",
          "packageWeight": 15,
          "priority": "HIGH"
        },
        {
          "orderId": "ORD002",
          "latitude": 28.4595,
          "longitude": 77.0266,
          "address": "Cyber Hub, Gurgaon, Haryana, India",
          "packageWeight": 30,
          "priority": "MEDIUM"
        }
      ]
    }
  ],
  "unassignedOrders": ["ORD015", "ORD022"]
}

unassignedOrders is a flat array of order ID strings (not full objects). It will be [] when all orders were successfully assigned.

totalDistance is the vehicle's cumulative travel distance across all its stops, formatted to 2 decimal places (e.g., "12.47 km").


πŸ“¦ Sample Inputs

A full 30-order / 5-vehicle dataset to validate the end-to-end flow.

β–Ά Click to expand β€” 30 Orders
{
  "orders": [
    { "orderId": "ORD001", "latitude": 28.6139, "longitude": 77.2090, "address": "Connaught Place, Delhi, India", "packageWeight": 15, "priority": "HIGH" },
    { "orderId": "ORD002", "latitude": 28.6139, "longitude": 77.2090, "address": "Connaught Place, Delhi, India", "packageWeight": 10, "priority": "MEDIUM" },
    { "orderId": "ORD003", "latitude": 28.7041, "longitude": 77.1025, "address": "Karol Bagh, Delhi, India", "packageWeight": 20, "priority": "LOW" },
    { "orderId": "ORD004", "latitude": 28.5355, "longitude": 77.3910, "address": "Sector 18, Noida, Uttar Pradesh, India", "packageWeight": 25, "priority": "HIGH" },
    { "orderId": "ORD005", "latitude": 28.4595, "longitude": 77.0266, "address": "Cyber Hub, Gurgaon, Haryana, India", "packageWeight": 30, "priority": "MEDIUM" },
    { "orderId": "ORD006", "latitude": 28.6139, "longitude": 77.2090, "address": "Connaught Place, Delhi, India", "packageWeight": 40, "priority": "LOW" },
    { "orderId": "ORD007", "latitude": 28.7041, "longitude": 77.1025, "address": "Karol Bagh, Delhi, India", "packageWeight": 20, "priority": "HIGH" },
    { "orderId": "ORD008", "latitude": 28.5355, "longitude": 77.3910, "address": "Sector 18, Noida, Uttar Pradesh, India", "packageWeight": 25, "priority": "HIGH" },
    { "orderId": "ORD009", "latitude": 28.4595, "longitude": 77.0266, "address": "Cyber Hub, Gurgaon, Haryana, India", "packageWeight": 15, "priority": "MEDIUM" },
    { "orderId": "ORD010", "latitude": 28.6139, "longitude": 77.2090, "address": "Connaught Place, Delhi, India", "packageWeight": 30, "priority": "LOW" },
    { "orderId": "ORD011", "latitude": 28.7041, "longitude": 77.1025, "address": "Karol Bagh, Delhi, India", "packageWeight": 20, "priority": "HIGH" },
    { "orderId": "ORD012", "latitude": 28.5355, "longitude": 77.3910, "address": "Sector 18, Noida, Uttar Pradesh, India", "packageWeight": 10, "priority": "MEDIUM" },
    { "orderId": "ORD013", "latitude": 28.4595, "longitude": 77.0266, "address": "Cyber Hub, Gurgaon, Haryana, India", "packageWeight": 25, "priority": "HIGH" },
    { "orderId": "ORD014", "latitude": 28.6139, "longitude": 77.2090, "address": "Connaught Place, Delhi, India", "packageWeight": 15, "priority": "LOW" },
    { "orderId": "ORD015", "latitude": 28.7041, "longitude": 77.1025, "address": "Karol Bagh, Delhi, India", "packageWeight": 20, "priority": "HIGH" },
    { "orderId": "ORD016", "latitude": 28.5355, "longitude": 77.3910, "address": "Sector 18, Noida, Uttar Pradesh, India", "packageWeight": 30, "priority": "LOW" },
    { "orderId": "ORD017", "latitude": 28.4595, "longitude": 77.0266, "address": "Cyber Hub, Gurgaon, Haryana, India", "packageWeight": 15, "priority": "MEDIUM" },
    { "orderId": "ORD018", "latitude": 28.6139, "longitude": 77.2090, "address": "Connaught Place, Delhi, India", "packageWeight": 10, "priority": "HIGH" },
    { "orderId": "ORD019", "latitude": 28.7041, "longitude": 77.1025, "address": "Karol Bagh, Delhi, India", "packageWeight": 20, "priority": "LOW" },
    { "orderId": "ORD020", "latitude": 28.5355, "longitude": 77.3910, "address": "Sector 18, Noida, Uttar Pradesh, India", "packageWeight": 30, "priority": "HIGH" },
    { "orderId": "ORD021", "latitude": 28.6139, "longitude": 77.2090, "address": "Connaught Place, Delhi, India", "packageWeight": 25, "priority": "MEDIUM" },
    { "orderId": "ORD022", "latitude": 28.7041, "longitude": 77.1025, "address": "Karol Bagh, Delhi, India", "packageWeight": 20, "priority": "LOW" },
    { "orderId": "ORD023", "latitude": 28.5355, "longitude": 77.3910, "address": "Sector 18, Noida, Uttar Pradesh, India", "packageWeight": 30, "priority": "HIGH" },
    { "orderId": "ORD024", "latitude": 28.4595, "longitude": 77.0266, "address": "Cyber Hub, Gurgaon, Haryana, India", "packageWeight": 15, "priority": "LOW" },
    { "orderId": "ORD025", "latitude": 28.6139, "longitude": 77.2090, "address": "Connaught Place, Delhi, India", "packageWeight": 20, "priority": "MEDIUM" },
    { "orderId": "ORD026", "latitude": 28.7041, "longitude": 77.1025, "address": "Karol Bagh, Delhi, India", "packageWeight": 25, "priority": "HIGH" },
    { "orderId": "ORD027", "latitude": 28.5355, "longitude": 77.3910, "address": "Sector 18, Noida, Uttar Pradesh, India", "packageWeight": 20, "priority": "MEDIUM" },
    { "orderId": "ORD028", "latitude": 28.4595, "longitude": 77.0266, "address": "Cyber Hub, Gurgaon, Haryana, India", "packageWeight": 15, "priority": "LOW" },
    { "orderId": "ORD029", "latitude": 28.6139, "longitude": 77.2090, "address": "Connaught Place, Delhi, India", "packageWeight": 30, "priority": "HIGH" },
    { "orderId": "ORD030", "latitude": 28.7041, "longitude": 77.1025, "address": "Karol Bagh, Delhi, India", "packageWeight": 20, "priority": "LOW" }
  ]
}
β–Ά Click to expand β€” 5 Vehicles
{
  "vehicles": [
    { "vehicleId": "VEH001", "capacity": 100, "currentLatitude": 28.7041, "currentLongitude": 77.1025, "currentAddress": "Karol Bagh, Delhi, India" },
    { "vehicleId": "VEH002", "capacity": 80,  "currentLatitude": 28.5355, "currentLongitude": 77.3910, "currentAddress": "Sector 18, Noida, Uttar Pradesh, India" },
    { "vehicleId": "VEH003", "capacity": 120, "currentLatitude": 28.4595, "currentLongitude": 77.0266, "currentAddress": "Cyber Hub, Gurgaon, Haryana, India" },
    { "vehicleId": "VEH004", "capacity": 90,  "currentLatitude": 28.6139, "currentLongitude": 77.2090, "currentAddress": "Connaught Place, Delhi, India" },
    { "vehicleId": "VEH005", "capacity": 110, "currentLatitude": 28.7041, "currentLongitude": 77.1025, "currentAddress": "Karol Bagh, Delhi, India" }
  ]
}

πŸ–₯️ H2 Database Console

The H2 web console is enabled and accessible while the app is running:

Setting Value
URL http://localhost:8080/h2-console
JDBC URL jdbc:h2:mem:testdb
Username sa
Password password

Use it to run ad-hoc SQL against the ORDERS and VEHICLES tables during development.

⚠️ H2 is an in-memory database. All data is cleared when the application restarts. Re-upload vehicles and orders after each restart.


πŸ§ͺ Running Tests

# macOS / Linux
./mvnw test

# Windows
mvnw.cmd test

Test Classes

Test Class Type What It Verifies
DispatchLoadBalancerApplicationTests Smoke Spring application context loads without errors
DispatchServiceTest Unit (Mockito) Priority sorting, capacity constraint rejection, nearest-vehicle greedy selection
DistanceCalculatorTest Unit (Mockito) Additional service-level edge-case scenarios with mocked repositories
DispatchIntegrationTest Integration (MockMvc) Full end-to-end: POST /vehicles β†’ POST /orders β†’ GET /plan, JSON response assertions

Key Scenarios Tested

Priority sorting β€” given a LOW order before a HIGH order in the DB, the HIGH order must appear at index 0 in assignedOrders.

Capacity constraint β€” an order with packageWeight: 60 against a vehicle with capacity: 50 must land in unassignedOrders, not assignedOrders, and totalLoad must remain 0.

Nearest vehicle selection β€” given V_NEAR at 5.0 km and V_FAR at 5,000 km, the order must be routed to V_NEAR.


⚠️ Error Handling

GlobalExceptionHandler (@RestControllerAdvice) handles two categories globally.

Validation Errors β€” 400 Bad Request

Triggered when a @NotNull-annotated field (e.g., orderId, vehicleId) is absent from the request body.

{
  "status": "error",
  "message": "Validation Failed",
  "errors": {
    "orderId": "Order ID cannot be null",
    "vehicleId": "Vehicle ID cannot be null"
  }
}

Unhandled Exceptions β€” 500 Internal Server Error

Catches all other exceptions and returns a clean message instead of a stack trace.

{
  "status": "error",
  "message": "<exception message>"
}

Controller-Level Input Guards

Before the service layer is ever reached, the controller performs an explicit empty-list check:

Condition HTTP Body
orders key missing or empty array 400 { "message": "No orders provided" }
vehicles key missing or empty array 400 { "message": "No vehicles provided" }
No data in DB when plan is requested 200 { "dispatchPlan": [], "unassignedOrders": [] }

πŸ“ Key Design Decisions

unassignedOrders is List<String> (IDs only) β€” The response only needs to flag which orders couldn't be placed. Full order objects would be redundant since they're already persisted in the DB and retrievable.

Secondary sort by weight descending β€” Within the same priority tier, heavier items are harder to fit as capacity depletes. Assigning them first improves overall fleet utilisation.

VehicleSimulationState inner class β€” The algorithm requires mutable in-memory state (remaining capacity, current GPS position, accumulated km) that must not leak into the immutable JPA Vehicle entity. The inner class isolates this transient routing state cleanly inside DispatchService.

Table name "orders" not "order" β€” ORDER is a reserved SQL keyword. The explicit @Table(name = "orders") annotation on the Order entity prevents a Hibernate DDL failure at startup.

spring.jpa.hibernate.ddl-auto=update β€” Schema is auto-managed by Hibernate on startup, keeping the setup completely zero-config for development and testing.


Β© 2026 Yogesh Shende. All Rights Reserved.

About

A high-performance Spring Boot application that solves the Vehicle Routing Problem (VRP) by optimally allocating delivery orders to a fleet of vehicles. It uses a custom Greedy Nearest-Neighbor Algorithm with the Haversine Formula to minimize total travel distance while strictly respecting priority tiers and vehicle capacity constraints.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages