A Spring Boot 3.2 / Java 17 service that generates monotonically increasing sequence values backed by PostgreSQL.
It is designed to reserve IDs in batches from the database, cache them in memory, and serve them safely under concurrent load.
- PostgreSQL-backed named sequences such as
ORDER,USER, andINVOICE - Batch reservation to reduce database round-trips
- In-memory range cache with per-sequence locking
- REST API for raw and formatted sequence values
- Structured error responses via a global exception handler
- Database bootstrap via
schema.sql
- Java 17
- Spring Boot 3.2.3
- Spring Web
- Spring Data JPA
- PostgreSQL
- Validation
- springdoc-openapi (dependency included)
src/main/java/com/example/seqgen/
├── SequenceGeneratorApplication.java # Application entry point
├── config/
│ └── SequenceProperties.java # sequence.* configuration
├── controller/
│ ├── GlobalExceptionHandler.java # JSON error handling
│ └── SequenceController.java # REST endpoints
├── entity/
│ └── Sequence.java # JPA entity for sequences table
├── repository/
│ └── SequenceRepository.java # Native SQL data access
└── service/
├── Range.java # In-memory reserved number window
└── SequenceGeneratorService.java # Core sequence generation logic
src/main/resources/
├── application.yml # Application and database settings
└── schema.sql # Table creation and seed data
- The service receives a request for a sequence type, such as
ORDER. - It first tries to serve the next value from an in-memory
Range. - If the range is missing or exhausted, it acquires a per-type lock.
- It then reserves the next batch from PostgreSQL using a single atomic
UPDATE ... RETURNINGstatement. - The reserved range is cached in memory and handed out one value at a time.
This design means:
- fast reads when a cached batch is available
- no overlapping ranges across concurrent application instances
- fewer database calls than reserving one value at a time
src/main/resources/schema.sql is executed on startup because application.yml sets:
spring:
sql:
init:
mode: alwaysThat means the script runs every time the application starts, not only on the first run.
The script:
- creates the
sequencestable if it does not exist - seeds the default sequence names:
ORDERUSERINVOICE
The SQL is idempotent, so re-running it is safe:
CREATE TABLE IF NOT EXISTSINSERT ... ON CONFLICT DO NOTHING
The default configuration is in src/main/resources/application.yml.
spring:
datasource:
url: jdbc:postgresql://localhost:5433/seqdb
username: postgres
password: postgressequence:
batch-size: 50
supported-types:
- ORDER
- USER
- INVOICENotes:
batch-sizecontrols how many values are reserved per database call.supported-typesis validated as non-empty and documents the intended default sequence names; the current startup script seeds the defaults directly.- The service currently accepts new types on first use by upserting them into the table.
Base path: /api/v1/sequences
GET /api/v1/sequences/{type}/nextExample:
GET /api/v1/sequences/ORDER/nextResponse:
{
"type": "ORDER",
"value": 42
}GET /api/v1/sequences/{type}/next/formattedExample:
GET /api/v1/sequences/ORDER/next/formattedResponse:
{
"type": "ORDER",
"value": "ORDER-000042"
}The project includes springdoc-openapi-starter-webmvc-ui, so Swagger UI and OpenAPI docs are available when the app is running.
- Swagger UI:
http://localhost:8080/swagger-ui/index.html - Alternate Swagger UI path:
http://localhost:8080/swagger-ui.html - OpenAPI JSON:
http://localhost:8080/v3/api-docs - OpenAPI YAML:
http://localhost:8080/v3/api-docs.yaml
Use Swagger UI to test these endpoints interactively:
GET /api/v1/sequences/{type}/nextGET /api/v1/sequences/{type}/next/formatted
The app returns structured JSON for common failures:
400 Bad Requestfor invalid input such as a blank sequence type500 Internal Server Errorfor unexpected or database-related failures
Example error body:
{
"error": "Sequence type must not be blank",
"timestamp": "2026-04-18T12:34:56.789Z"
}SequenceGeneratorServicenormalizes types to uppercase.Rangeis an in-memory window backed byAtomicLong.- Each sequence type has its own
ReentrantLock, so different types do not block each other during range refresh. - The repository uses native PostgreSQL SQL for atomic range reservation.
spring.sql.init.mode=alwaysmeans schema initialization happens on every startup.- There are currently no checked-in automated tests under
src/test/java.
- Java 17
- Maven 3.9+ recommended
- PostgreSQL running on
localhost:5433with a database namedseqdb
This repository already includes a docker-compose.yml file for PostgreSQL. It runs the database in Docker while the Spring Boot application runs on your machine.
docker compose up -d dbThis starts a PostgreSQL 16 container with:
- database:
seqdb - user:
postgres - password:
postgres - host port:
5433
The app is already configured to connect to:
jdbc:postgresql://localhost:5433/seqdbSo no code changes are needed if you use the default Compose setup.
docker compose downTo remove the persisted database data as well:
docker compose down -v- The
seqdb-datavolume keeps database data across restarts. - There is currently no
Dockerfilefor the Spring Boot application itself. - If you later containerize the app too, the datasource host should change from
localhostto the Compose service namedb.
- Create the PostgreSQL database and user if needed.
- Or start PostgreSQL with Docker using the commands above.
- Make sure the database is reachable at the configured host and port.
- Start the application:
mvn spring-boot:runOr build and run the jar:
mvn clean package
java -jar target/sequence-generator-1.0.0.jarcurl http://localhost:8080/api/v1/sequences/ORDER/next
curl http://localhost:8080/api/v1/sequences/ORDER/next/formattedNo license file is currently included in the repository.