How hrva.cc Handles Security, Speed, Reliability, and Migration
A deep dive into the architecture behind hrva.cc — how we keep links fast, safe, and always available through Redis caching, JWT auth, Safe Browsing, and zero-downtime migrations.
Building a URL shortener sounds simple: accept a long URL, generate a short code, redirect when someone visits it. But making that redirect fast, secure, and reliable at scale — while evolving the platform without downtime — is where the real engineering lives. Here's how hrva.cc is built.
Stack Overview
hrva.cc runs on a Spring Boot 4.0.6 backend (Java 25) with a Next.js 16 frontend (React 19). The backend uses PostgreSQL for persistent storage, Redis for caching, and MailHog for email in development. The frontend is deployed on Cloudflare Workers, and the backend runs in Docker with multi-arch builds (amd64 + arm64) via GitHub Actions.
Security: Defense in Depth
Threat Scanning at Every Level
Every URL shortened through hrva.cc is checked against the Google Safe Browsing API v4 before it's saved. If the destination is flagged as malware, phishing, or social engineering, the link is blocked at creation time. But threats evolve — a safe site today can be compromised tomorrow. That's why a scheduled task rechecks every active URL against Safe Browsing daily at 2 AM. Flagged URLs are automatically deactivated, evicted from cache, and the owner is notified by email.
JWT Authentication
All authenticated requests use stateless JWT tokens with configurable expiry (default 10 hours).
Tokens are signed with a server-side secret and verified on every request through a custom
JwtFilter that sits in the Spring Security chain. No session state, no server-side
token storage — just cryptographic verification.
Rate Limiting
Login attempts are rate-limited using a Guava in-memory cache — 20 attempts per IP per day. After that, the IP is blocked until the window expires or an admin clears the limit from the dashboard. The admin panel also shows a live view of all active rate limits with a one-click clear button.
OWASP Basics
Actuator endpoints are locked down: /actuator/health is public, everything else
requires ROLE_ADMIN. CORS is configured to only allow the frontend origin.
The ForwardedHeaderFilter ensures correct IP resolution behind proxies.
Input validation happens at the controller, service, and entity levels.
Speed: Every Millisecond Counts
Redis Caching
The redirect endpoint (GET /{short}) is the hottest path in the system — every click
on every short URL goes through it. Initially it made two database queries per redirect. Now it's
cache-first: on every request, we check Redis. If the URL data is cached and still valid
(not expired, not over its visit limit, not deactivated), we serve the redirect from cache
without touching the database. Visit tracking happens asynchronously in the background.
Cache Warmup and Priming
On application startup, the CacheWarmup component loads the 20 most visited,
20 most recent, and 20 most recently accessed URLs into Redis — so the most popular links
are hot from the start. New URLs are cached immediately on creation via
cacheUrlResponse(). The TTL is set to 7 days, but stale data is caught by
validation logic on every cache hit. Redis graceful degradation means if Redis is down,
we fall through to the database without error.
Read-Optimized Transactions
All service-layer methods default to @Transactional(readOnly = true). Only write
operations explicitly override this. This gives the database query planner better optimization
hints and prevents accidental writes in read paths.
Frontend Performance
The Next.js frontend uses server-side rendering for initial page loads, then takes over as a
single-page app. The landing page is static and prerendered. Fonts are loaded via
next/font with swap display. Animations use CSS-only keyframes (no JavaScript
animation libraries) with zero external runtime dependencies for styling.
Reliability: Built to Handle Failures
Cache Graceful Degradation
Redis is a performance accelerator, not a hard dependency. A custom CacheErrorHandler
catches every Redis failure — get, put, evict, clear — logs it, and returns null, causing the
application to fall through to the database. A Redis outage means slightly slower requests, not
downtime.
SafeBrowsing Graceful Degradation
The Google Safe Browsing API client is a singleton that retries on failure. If the API is unreachable, it returns an empty threat list — URLs are not blocked due to an API outage. The daily recheck task will catch any threats once the API is available again.
Async Visit Tracking
Visit counting is fully asynchronous. When someone clicks a short link, the redirect response is
sent immediately, and a background task increments the visit counter using Spring's
TaskExecutor. This means the redirect response time is independent of database
write latency. Visit limits are checked on every redirect via the cached data, and the
async write enforces the final deactivation.
Health Monitoring
The admin dashboard shows real-time status of PostgreSQL, Redis, JVM heap, uptime, and request counts. The system health page provides green/red indicators for every service layer. Micrometer metrics track redirect response times (cache hit vs. cache miss), and the admin panel displays average and max redirect latency.
Migration: Evolve Without Downtime
Liquibase for Schema Changes
All database changes go through Liquibase changelogs — versioned, sequential, and reversible. Migrations run automatically on application startup. The changelog has 9 migrations covering the full schema evolution from initial users table to audit logging and email tracking.
Angular to React Rewrite
The frontend was originally built in Angular 14. It was completely rewritten to Next.js 16
(React 19) in 2026. The rewrite was done alongside the existing Angular deployment, with
the new frontend deployed to a separate subdomain (app.hrva.cc) while the
old Angular app remained at the root domain. Once the new frontend was fully tested,
the root domain was switched via a 302 redirect.
Gradle to Maven Migration
The backend was migrated from Gradle to Maven for faster builds and simpler CI configuration.
This was a single-commit migration that touched only build files and directory structure —
zero runtime code changes. The CI pipeline was updated simultaneously to use mvn package
instead of gradle build.
Docker Multi-Arch CI
Every push to main triggers a GitHub Actions workflow that runs all 150+ backend
tests, builds the application, and pushes a multi-arch Docker image (amd64 + arm64) to
GitHub Container Registry. PRs run only the build and tests — no image push. Production
deployments pull the image via Portainer with environment variables for OAuth2 secrets.
Deployment Safety
Rolling updates ensure zero-downtime deploys. Database schema changes via Liquibase are backward-compatible: new columns are added before code that reads them is deployed, and removed only after no running code references them. The scheduled SafeBrowsing recheck and URL deactivation tasks have configurable schedules in case maintenance windows are needed.
The Numbers
As of May 2026: 150+ automated tests covering controllers, services, validators, converters, and models. All tests run on every commit. Redis handles thousands of cache lookups with a 7-day TTL.