Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
227: Building a Simple URL Shortener Service
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
UrlMappingthat stores both thelongUrland aLocalDateTime expirationDate. - Update the
shortToLongmap to storeUrlMappingobjects instead of simple strings. - Modify the
getLongUrlmethod 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 returnnull(which will trigger the 404 Not Found response in your controller). - Update the
shortenUrlmethod to accept an optionaldaysUntilExpirationparameter.
There are no comments for now.