A URL shortener with per-click analytics, built with Java 24, Spring Boot 4, PostgreSQL, and Redis.
- Base62 short codes derived from the database primary key (plus a 62³ offset) — collision-free by construction, no random generation or retry loops.
- Redis cache-aside layer over redirect lookups (10-minute TTL) — hot links resolve without touching PostgreSQL.
- Click analytics captured on every redirect (timestamp, referrer, user-agent) and exposed as aggregate metrics (total clicks, clicks per day, top referrers) through a stats API.
📖 New to Spring Boot? TUTORIAL.md walks through this entire codebase lesson by lesson, written for developers coming from Node + Express.
| Method | Path | Description |
|---|---|---|
POST |
/api/urls |
Create a short link. Body: {"url": "https://..."} → 201 + link details |
GET |
/{shortCode} |
Redirect (302) to the original URL; records a click |
GET |
/api/urls/{shortCode} |
Link details |
GET |
/api/urls/{shortCode}/stats |
Aggregate analytics for the link |
GET |
/health |
Liveness check |
Errors follow RFC 7807 (application/problem+json); validation failures include a per-field error map.
curl -X POST http://localhost:8080/api/urls \
-H "Content-Type: application/json" \
-d '{"url":"https://github.com"}'
# {"shortCode":"1001","shortUrl":"http://localhost:8080/1001", ...}
curl -i http://localhost:8080/1001 # 302 → Location: https://github.com
curl http://localhost:8080/api/urls/1001/stats
# {"totalClicks":5,"clicksByDay":[{"day":"2026-07-18","clicks":5}],
# "topReferrers":[{"referrer":"(direct)","clicks":2}, ...]}POST /api/urls GET /{code} GET /api/urls/{code}/stats
│ │ │
UrlController RedirectController UrlController
│ │ │
UrlService 1. Redis GET url:{code} ── hit ──► 302 UrlService.getStats
INSERT urls miss ▼ │
code = Base62(62³+id) 2. SELECT urls → SET (TTL 10m) GROUP BY day / referrer
│ 3. INSERT clicks │
PostgreSQL (urls ←FK─ clicks) Redis (url:{code}) PostgreSQL
Redirects use 302 (not 301) deliberately: a 301 is cached by browsers, so subsequent clicks would bypass the server and never be counted.
Prerequisites: Java 17+, Docker.
docker compose up -d # PostgreSQL :5432, Redis :6380
./mvnw spring-boot:run # Windows: .\mvnw.cmd spring-boot:runApp starts on http://localhost:8080:
/— demo UI: shorten links and view analytics from the browser/swagger-ui.html— interactive OpenAPI docs (try every endpoint live)
Configuration lives in src/main/resources/application.yml.
src/main/java/com/abhiram/urlshortener/
├── controller/ UrlController, RedirectController, HealthController
├── service/ UrlService (create/resolve/stats + cache), ClickService
├── repository/ UrlRepository, ClickRepository (derived + native queries)
├── model/ Url, Click (JPA entities)
├── dto/ request/response records
├── exception/ UrlNotFoundException, GlobalExceptionHandler (RFC 7807)
└── util/ Base62
The included multi-stage Dockerfile builds a slim production image. Configuration is environment-driven (SPRING_DATASOURCE_URL, SPRING_DATA_REDIS_URL, APP_BASE_URL, PORT), so it deploys cleanly to Render (app) + Neon (Postgres) + Upstash (Redis) — see TUTORIAL.md Lesson 10 for the step-by-step.
Async click recording, negative caching for unknown codes, Flyway migrations, custom aliases, link expiry, rate limiting, Testcontainers-based integration tests.