A clean, layered URL shortener built with Spring Boot, JPA, Thymeleaf UI, and an optional K6 load test. It follows controller/service/repository architecture, uses records for DTOs, and includes a Snowflake-based ID generator with Base62 encoding.
- Java 25 (Gradle toolchains will auto-provision if not installed)
- Gradle Wrapper (included)
- Optional for load tests: k6
./gradlew clean bootRunApp starts at http://localhost:8080 using in-memory H2.
H2 console (optional): http://localhost:8080/h2-console
- JDBC URL:
jdbc:h2:mem:shortnerdb - Username:
sa - Password: (empty)
Open http://localhost:8080/ and:
- Enter Long URL
- Optionally provide Custom Alias and User UUID
- Submit to get a short URL
- Shorten a URL (optional
user_uuidheader):
curl -s -X POST 'http://localhost:8080/api/v1/shorten' \
-H 'Content-Type: application/json' \
-H 'user_uuid: user-123' \
-d '{"longUrl":"https://example.com/very/long/path?param=value"}'- Redirect (will return 301 with Location):
curl -I 'http://localhost:8080/{shortCode}'- User metrics (count of shortened URLs per user):
curl -s 'http://localhost:8080/api/v1/metrics/users'- Redirects use HTTP 301 with headers:
Cache-Control: private, max-age=90X-Robots-Tag: noindex
- Service expects optional
user_uuidheader; if present, it is stored on the mapping for metrics. - DTOs are Java records (
ShortenRequest,ShortenResponse).
A k6 script is provided to simulate read-heavy traffic with seeding.
load-test/url-shortener.k6.js
k6 run load-test/url-shortener.k6.jsBASE_URL(defaulthttp://localhost:8080)API_PATH(default/api/v1/shorten)DURATION(default1m)READ_RPS(default100)WRITE_RPS(default1)SEED_COUNT(default100)RANDOM_SEED(default12345)
- Increase duration and rates:
BASE_URL=http://localhost:8080 \
DURATION=2m \
READ_RPS=200 \
WRITE_RPS=2 \
k6 run load-test/url-shortener.k6.js- Increase seed size:
SEED_COUNT=500 k6 run load-test/url-shortener.k6.jsThe script:
setup()seedsSEED_COUNTshort URLs via POST.redirectsscenario generates GET/{shortCode}requests (301 expected).shortenscenario generates POST requests to create new mappings.- Checks verify status codes and the
Locationheader on redirects.
- H2 is in-memory by default:
spring.jpa.hibernate.ddl-auto=update
spring.jpa.open-in-view=false
spring.h2.console.enabled=true
spring.thymeleaf.cache=false
spring.datasource.url=jdbc:h2:mem:shortnerdb;DB_CLOSE_DELAY=-1;MODE=MySQL
spring.datasource.username=sa
spring.datasource.password=
- Snowflake defaults (override via env/properties):
snowflake.datacenter.id=1
snowflake.machine.id=1
- Class diagram:
docs/class-diagram.md - Sequence diagrams:
docs/sequence-diagram.md
src/main/java/.../web— Controllers (API, Web UI, Redirect)src/main/java/.../core— Service interface and implementationsrc/main/java/.../model— JPA entitiessrc/main/java/.../repository— Spring Data JPA repositorysrc/main/java/.../util— Snowflake + Base62 utilitiessrc/main/resources/templates— Thymeleaf templates (UI)load-test— k6 scriptdocs— Diagrams and documentation
- For a fixed 6-character short code, map IDs into the 62^6 space and left-pad; current Base62 output length varies with magnitude (Snowflake → typically 10–11 chars over time). The service performs DB uniqueness checks on insert.