docker compose up -d
./mvnw spring-boot:run
# Or build JAR
./mvnw clean package -DskipTests
java -jar target/url-shortener-0.0.1-SNAPSHOT.jar
curl -X POST http://localhost:8080/shorten \
-H "Content-Type: application/json" \
-d '{"url": "www.google.com"}'We use UUIDv7 for internal IDs and keep short_code as a separate unique field. Compared with BIGSERIAL, UUIDv7 avoids exposing growth patterns and does not require centralized sequence coordination across distributed writers. Compared with UUIDv4, UUIDv7 keeps the same coordination-free generation model but improves index locality because values are time-ordered.
The Strategy Design pattern is used to switch between different approaches. We need to keep in mind that each approach however is affects by extra details, like type/existence of a centralized db icrement counter or a UUID identifier, possibility of collisions and hence of retry logic in the application level.
An approach for short codes is CRC32 hash for simplicity. This is acceptable for a small learning project, but CRC32 collisions are possible, so production behavior should include uniqueness checks with retry logic or a move to a stronger random Base62 generator.
We use random base62 generator(not counter based).
The birthday problem asks: in a group of n people, what's the probability that two share a birthday? Intuitively you'd think you need ~365 people, but it's actually just 23 for a 50% chance.
Where n is the number of URLs, and k is the keyspace size
- At 1 million URLs → collision chance ≈ 0.00014% (negligible)
- At 10 million URLs → collision chance ≈ 1.4%
- At 100 million URLs → collision chance ≈ 75%
At very large scale we'd either increase code length to 8-9 or switch to a base62 counter-based approach.
Base62 counter-based conversion:
First we need the url's icrement counter. E.g.
Worth noting that with hashing, same input would always produce same short code.
We use 302 Found (temporary redirect) rather than 301 Moved Permanently. A 301 is cached by the browser, subsequent requests for the same short code bypass our server entirely and go straight to the destination. A 302 ensures every request passes through us, enabling future click analytics, referrer tracking, and the ability to update or expire destination URLs. The tradeoff is a small latency cost per redirect, which a caching layer (e.g. Redis) would mitigate at scale.
Schema changes are managed through versioned Flyway migrations and applied forward-only. We avoid manual schema edits so environments remain reproducible and changes are reviewable in source control.
- Bytebase: Choose Primary Key UUID or Auto Increment https://www.bytebase.com/blog/choose-primary-key-uuid-or-auto-increment/
- Birthday Problem: Math behind collision probability https://en.wikipedia.org/wiki/Birthday_problem
