This is a Spring Boot 3.x (Java 17) REST API that simulates a toy truck robot moving around on a square or rectangle table.
The entire API surface is defined in an OpenAPI 3.0 specification (src/main/resources/openapi/truck-robot.yaml).
During the Maven build, the openapi-generator-maven-plugin automatically generates:
- Data models (
SimulationResponse,CommandRequest, etc.) - REST controller interfaces (with
CompletableFuturereturn types for async rest-api, or plain objects for sync rest-api)
I just implemented those generated interfaces from OpenAPI
- Java 17
- Maven 3.9+
- Free port (default: 8081 for async-soln1, 8082 for async-soln2 and 8083 for sync-slon3)
Verify: java -version mvn -version
mvn clean package mvn clean install
mvn clean package -DskipTests
The application supports three mutually exclusive Spring profiles:
- Asynchronous REST implementation (solution 1) \
- that uses my custom built WorkflowParallelSequencer \
- built on top of LMAX Disruptor/RingBuffer - avoids expensive locking(threads never block during request event submission), pre-allocated events for low GC \
- all workflow events belonging a sequenceKey (robot) are guaranteed to be sequenced on to single lane (disruptor/ringbuffer) \
- hash bucket index = sequenceKey modulo concurrencyLevel which is configurable \
- this sequecing avoids the need to implement locking / any expensive thread-safety mechanims on a Robot StateMachine \
- scales by parallelizing workflows of unrealated sequenceKeys but by guaranteed sequencing of all workflow events pertaining to the same sequenceKey (robotId in our case) \
- accordingly the RobotStores are also sharded \
- refer to CoreWorkflowParallelSequencer.java and LMAXDisruptorWorkflowSequencer.java in the project java project caterpillar-commons-lib - these are custom implemented by me \
- Asynchronous REST implementation (solution 2)
- that uses my custom built ParallelSequenceExecutor
- built on top of JDKs SingleThreadPoolExecutors
- follows similar parallel-sequecing mechanism as async-soln1 but it uses the standard JDK's Single ThreadPoolExecutor(poolSize=1) instead of LMAX disruptor/ringbuffer
- a single ThreadPool executor gives the same sequenncing semantics - all tasks submitted to it are guranteed to be executed in sequence \
- so it also scales, but weaker than the async-soln1 because the source of serialization can still be contending for the task-queue (a blocking queue) when there hashing collisions
- accordingly the RobotStores are also sharded
- refer to ParallelSequenceExecutor.java and ParallelSequenceThreadPoolExecutor.java in the project java project caterpillar-commons-lib - these are custom implemented by me
- Synchronous REST implementation (solution 3)
- that does NOT use any of the above sequencing mechansims and does not scale as well above the above mechanisms
- since it does not do sequencing, we will have to guard the Robot's StateMachine during single or batch commands using a thick lock.
- accordingly the RobotStores are sharded
- however it doesnt just give up but tries to fare way better by lock-stripping to guard each partition separately with a thick partition lock
- also the locks used are Rentrant & FAIR locks to allow for reasonable fairness among the requests
Exactly one of these profiles must be active at runtime or the application will fail to run throwing an exception.
The pom.xml defines exec-maven-plugin executions that start the server with the correct JVM flags and profile.
From the CLI:
async-soln1
mvn -DskipTests exec:exec@robot-restapi-async-soln1
async-soln2
mvn -DskipTests exec:exec@robot-restapi-async-soln2
sync-soln3
mvn -DskipTests exec:exec@robot-restapi-sync-soln3
Stop the server with Ctrl + C.
From the CLI: \
async-soln1
mvn -DskipTests spring-boot:run -Dspring-boot.run.profiles=async-soln1
async-soln2
mvn -DskipTests spring-boot:run -Dspring-boot.run.profiles=async-soln2
sync-soln3
mvn -DskipTests spring-boot:run -Dspring-boot.run.profiles=sync-soln3
On startup you should see a log similar to: The following 1 profile is active: "async-soln1"
Swagger UI: http://localhost:8081/swagger-ui/index.html
OpenAPI JSON: http://localhost:8081/v3/api-docs
-Dspring-boot.run.arguments=--server.port=8082
spring-boot:run example: mvn spring-boot:run -Dspring-boot.run.profiles=async-soln1 -Dspring-boot.run.arguments=--server.port=8082
Base path: /api/v1
All endpoints return JSON. Error responses follow this shape:
JSON{
"error": {
"code": "INVALID_REQUEST",
"message": "some descriptive text",
"traceId": "abc123..."
}
}
POST /api/v1/simulations
Optional: specify table size (defaults to 5×5).
Bashcurl -X POST http://localhost:8081/api/v1/simulations
-H "Content-Type: application/json"
-d '{
"table": {
"length": 5,
"breadth": 5
}
}'
→ Returns 201 Created + Location header
GET /api/v1/simulations/{simulationId}
Bashcurl http://localhost:8081/api/v1/simulations/1
DELETE /api/v1/simulations/{simulationId}
Bashcurl -X DELETE http://localhost:8081/api/v1/simulations/1
POST /api/v1/simulations/{simulationId}/commands
Supported commands: PLACE, MOVE, LEFT, RIGHT, REPORT
Optional idempotency: Idempotency-Key header
Examples:
Bash# PLACE
curl -X POST http://localhost:8081/api/v1/simulations/1/commands
-H "Content-Type: application/json"
-d '{
"type": "PLACE",
"x": 0,
"y": 0,
"facing": "NORTH"
}'
curl -X POST http://localhost:8081/api/v1/simulations/1/commands
-H "Content-Type: application/json"
-d '{"type": "MOVE"}'
curl -X POST http://localhost:8081/api/v1/simulations/1/commands
-H "Content-Type: application/json"
-d '{"type": "REPORT"}'
Response always includes final report:
"0,3,NORTH" or "ROBOT_NOT_PLACED"
POST /api/v1/simulations/{simulationId}/commands:batch
Up to 25 commands, executed sequentially, single final report returned.
Bashcurl -X POST http://localhost:8081/api/v1/simulations/1/commands:batch
-H "Content-Type: application/json"
-d '{
"commands": [
{"type": "PLACE", "x": 1, "y": 2, "facing": "EAST"},
{"type": "MOVE"},
{"type": "LEFT"},
{"type": "REPORT"}
]
}'
My code is mostly self explanatory.
From caterpillar-truck-robot java project
com.caterpillar.robotics.truck.domain.robot
- Robot.java
- RobotStateSnapshot.java
- RobotTest.java (extensive unit tests)
From caterpillar-commons-lib (Generic libraries that can be used in any context)
com.caterpillar.commons.util.spatial.twod
- CoordinateSpace.java
- Direction.java
- MutablePositionVector.java
- PositionVector.java
- PositionVectorOps.java
- PositiveQuadrantGrid.java
From caterpillar-truck-robot java project \
- TruckRobotAsyncRestController.java
- RobotSimulationService.java
- ParallelSeqSequencerRobotSimulationService.java
- RobotWorkflowService.java
- CoreRobotWorkflowService.java
- InMemoryRobotStore.java
- RobotStore.java
- ShardedRobotStore.java
ThreadingModel: From caterpillar-commons-lib (Generic libraries that can be used in any context) \
- CoreWorkflowParallelSequencer.java
- LMAXDisruptorWorkflowSequencer.java
- SequencedEventsProcessor.java
- SequencerEvent.java
- WorkflowParallelSequencer.java
- WorkflowSequencer.java
Domain Model \
- RobotCommand.java
- RobotCommandType.java
- RobotWorkflowEvent.java
- RobotWorkflowEventType.java
- ZeroGCRobotWorkflowEvent.java
Spring Wiring \
- Soln1AppConfig.java
- CommonConfig.java
- SequencedServiceCommonConfig.java
From caterpillar-truck-robot java project \
- TruckRobotAsyncRestController.java
- RobotSimulationService.java
- ParallelSeqSequencerRobotSimulationService.java
- RobotWorkflowService.java
- CoreRobotWorkflowService.java
- InMemoryRobotStore.java
- RobotStore.java
- ShardedRobotStore.java
ThreadingModel: From caterpillar-commons-lib (Generic libraries that can be used in any context) \
- ParallelSequenceExecutor.java
- ParallelSequenceThreadPoolExecutor.java
- ExecutorServices.java
- ExecutorUtils.java
Domain Model \
- RobotCommand.java
- RobotCommandType.java
- RobotWorkflowEvent.java
- RobotWorkflowEventType.java
- GarbagyRobotWorkflowEvent.java
Spring Wiring \
- Soln2AppConfig.java
- CommonConfig.java
- SequencedServiceCommonConfig.java
From caterpillar-truck-robot java project \
- TruckRobotSyncRestController.java
- RobotSimulationService.java (com.caterpillar.robotics.truck.solution3.api.syncrest)
- RobotWorkflowService.java
- PartitionLockGuardedRobotWorkflowService.java
- InMemoryRobotStore.java
- RobotStore.java
- ShardedRobotStore.java
Domain Model \
- RobotCommand.java
- RobotCommandType.java
- RobotWorkflowEvent.java
- RobotWorkflowEventType.java
- GarbagyRobotWorkflowEvent.java
Spring Wiring \
- Soln3AppConfig.java
- CommonConfig.java
- Api2DomainUtils.java
- ApiExceptionHandler.java
- Domain2ApiUtils.java
- Have annotated with custom @Garbage annotation to mark that I know this code-block generates garbage objects.
- Spring WebFlux based implementation - reactive-style
- GRPC equivalent of the REST-API
- Automated JBehave scenario tests
- Completely low GC approaches (async requests & responses correlated, the flow allowing for a fully ZERO GC approach atleast in the app layer, not in SpringMVC though which is not in our control)
- Replacing the heavy weight TOMCAT server with leaner Undertow server and enabling it to use Netty's native epoll transport instead of the default Java NIO transport for better network performance
For quick interactive testing without curl or Postman, open the provided HTML file: /caterpillar-truck-robot/testing-html-client-page/robot-sim-client-v2.html (open it in a browser) This is a simple, self-contained HTML + JavaScript page that acts as a console for the entire API.
How to use it:
- Start the server (any profile)
- Open robot-sim-client-v2.html in Chrome/Firefox/Edge
- Set your base URL if not on localhost:8081
- Click buttons to fire requests — watch the response pane at the bottom
I used it for manual server-side debugging.