Skip to Content
Course content

227: Building a Simple URL Shortener Service

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've probably used Bitly or TinyURL a thousand times, but when you actually sit down to build one, the first thing you realize is that it's not really about "shortening" a string. It's about creating a mapping. You aren't shrinking the URL; you're creating a key that points to a value in a database.

How do I generate a short key that isn't just a random string of characters?

A lot of people start by hashing the long URL with MD5 or SHA-256 and taking the first few characters. Don't do that. You'll run into collisions almost immediately, and handling those collisions makes your code messy. Instead, I recommend using a counter (like an auto-incrementing ID from your database) and converting that number into Base62.

Base62 uses 0-9, a-z, and A-Z. It's perfect because it's URL-safe and compact. If your ID is 1,000,000, the Base62 version is only 3 or 4 characters long. Here is a simple way to implement the encoder:

public class Base62Converter {
    private static final String ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
    public static String encode(long input) {
        StringBuilder sb = new StringBuilder();
        while (input > 0) {
            sb.append(ALPHABET.charAt((int) (input % 62)));
            input /= 62;
        }
        return sb.reverse().toString();
    }
}

It's a simple loop. I've used StringBuilder here because we're doing a lot of appending, and it's much more efficient than concatenating strings in a loop.

Where should I store these mappings if I want the service to be fast?

For a production app, you'd use Redis or a relational database. But for a learning project or a small internal tool, a ConcurrentHashMap is your best friend. Since your service will likely be handling multiple requests at once, a standard HashMap will eventually throw a ConcurrentModificationException or just corrupt your data.

You want a service layer that handles the logic of "give me a short key for this long URL." If the URL already exists, you should return the existing key rather than creating a duplicate.

public class UrlService {
    private final Map<String, String> shortToLong = new ConcurrentHashMap<>();
    private final Map<String, String> longToShort = new ConcurrentHashMap<>();
    private long counter = 1000000L; // Start high to ensure a decent length key

    public String shortenUrl(String longUrl) {
        if (longToShort.containsKey(longUrl)) {
            return longToShort.get(longUrl);
        }
        
        String shortKey = Base62Converter.encode(counter++);
        shortToLong.put(shortKey, longUrl);
        longToShort.put(longUrl, shortKey);
        return shortKey;
    }

    public String getLongUrl(String shortKey) {
        return shortToLong.get(shortKey);
    }
}

How do I actually handle the redirect in Java?

This is the part where the "magic" happens. When a user visits yourdomain.com/aB12, your server shouldn't return a webpage. It should return an HTTP 301 (Permanent Redirect) or 302 (Found) response. This tells the browser, "Hey, the content you're looking for is actually over here," and the browser automatically makes a second request to the original long URL.

If you're using Spring Boot, you can do this very cleanly using ResponseEntity. I prefer 302 for shorteners because it prevents browsers from caching the redirect too aggressively, which is useful if you ever want to track analytics on how many people clicked the link.

@GetMapping("/{shortKey}")
public ResponseEntity<Void> redirect(@PathVariable String shortKey) {
    String longUrl = urlService.getLongLongUrl(shortKey);
    
    if (longUrl == null) {
        return ResponseEntity.notFound().build();
    }
    
    return ResponseEntity.status(HttpStatus.FOUND)
            .location(URI.create(longUrl))
            .build();
}

Notice the .location() method. That sets the Location header in the HTTP response, which is exactly what the browser looks for to know where to go next.




📋 Practical Task

Implement a URL Expiration System

In a real-world scenario, you can't store every URL forever, or your memory/disk will fill up. Your task is to modify the UrlService to support expiring links.

  • Create a new class UrlMapping that stores both the longUrl and a LocalDateTime expirationDate.
  • Update the shortToLong map to store UrlMapping objects instead of simple strings.
  • Modify the getLongUrl method so that it checks if the current date is after the expiration date. If the link has expired, it should remove the entry from the map and return null (which will trigger the 404 Not Found response in your controller).
  • Update the shortenUrl method to accept an optional daysUntilExpiration parameter.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.