This repository contains a small Spring Boot tutorial for practicing key-value storage and Redis list operations.
Students complete the TODOs in the production code and use the tests to check each step. The tutorial is split into three tasks:
- Task 1: implement an embedded in-memory storage for bad login attempts.
- Task 2: implement the same storage with Redis and TTL.
- Task 3: implement a Redis-backed FIFO task queue.
Install and verify these tools before starting:
- JDK 25.
- Docker Desktop, Rancher Desktop, Colima, or another Docker daemon that is running and reachable from the terminal.
- Internet access for the first Maven dependency download.
Check your environment:
java -version
javac -version
docker infoIf Maven reports release version 25 not supported, your active java or javac is not JDK 25.
If tests report Could not find a valid Docker environment, Docker is not running or Testcontainers cannot reach it.
Run the full test suite:
Windows PowerShell:
.\mvnw.cmd testmacOS or Linux:
./mvnw testRun one task at a time:
Windows PowerShell:
.\mvnw.cmd test "-Dtest=TaskNo1_1Test"
.\mvnw.cmd test "-Dtest=TaskNo1_2Test"
.\mvnw.cmd test "-Dtest=TaskNo2Test"
.\mvnw.cmd test "-Dtest=TaskNo3Test"macOS or Linux:
./mvnw test -Dtest=TaskNo1_1Test
./mvnw test -Dtest=TaskNo1_2Test
./mvnw test -Dtest=TaskNo2Test
./mvnw test -Dtest=TaskNo3TestTasks 2 and 3 use Testcontainers to start Redis automatically, so Docker must be available even though you do not need to install Redis manually.
Implement in-memory key-value storage to track failed login attempts and integrate it with Authenticator.
The authenticator should:
- Increase the counter after a bad password.
- Lock a user out when the counter reaches the configured threshold.
- Reset the counter after a successful login.
Complete these private methods in Authenticator:
increaseBadLoginAttemptsCounter(String email)isLockedOut(String email)resetCounter(String email)
Use the email address as the storage key.
Expected behavior:
increaseBadLoginAttemptsCountercallsbadLoginAttemptsStorage.increment(email).isLockedOutreads the counter and compares it withbadLoginAttemptsThreshold.resetCounterremoves the email from storage.
This class should store counters in a HashMap.
Create this field:
private final Map<String, Integer> storage = new HashMap<>();Then implement:
get(String key)put(String key, Integer value)increment(String key)remove(String key)
Expected behavior:
- Missing keys return
0. putstores the provided value.incrementchanges a missing key from0to1.incrementadds1to an existing value.removedeletes the key.
Run:
.\mvnw.cmd test "-Dtest=TaskNo1_1Test,TaskNo1_2Test"Implement Redis-backed storage for bad login attempts. Values should expire automatically after the configured TTL.
The class uses RedisTemplate and ValueOperations. The TTL is injected as a Duration, so do not hardcode seconds inside the storage class.
Redis connection details are provided by Spring Boot and Testcontainers during tests. Do not create your own JedisConnectionFactory; use the configured RedisTemplate that is already injected into the storage class.
The authentication Redis template is configured with serializers for you. Integer values use a serializer compatible with Redis INCR, so redisOperations.increment(redisKey) can update the stored counter.
Complete these methods:
get(String key)put(String key, Integer value)increment(String key)remove(String key)setTtl(String key)
Expected behavior:
- Use
getKey(key)for every Redis operation so all keys are prefixed withbad_login:. getreturns0when Redis returnsnull.putstores the value and refreshes the TTL.incrementincrements the value and refreshes the TTL.removedeletes the Redis key.setTtlapplies the configured TTL.
Useful Redis operations:
redisOperations.get(redisKey)redisOperations.set(redisKey, value)redisOperations.increment(redisKey)redisOperations.getAndExpire(redisKey, ttl)redisOperations.getOperations().delete(redisKey)
Run:
.\mvnw.cmd test "-Dtest=TaskNo2Test"Implement a Redis-backed task queue where each user has a separate FIFO queue.
FIFO means the first task pushed for a user is the first task popped for that user.
Complete these methods:
push(String user, String task)pop(String user)clear(String user)
Expected behavior:
- Use
getKey(user)for every Redis operation so all queue keys are prefixed withtask:. pushappends a task to the user's queue.popreturns and removes the oldest task from the user's queue.popreturnsnullwhen the queue is empty.cleardeletes the user's queue.
Useful Redis operations:
redisOperations.rightPush(redisKey, task)redisOperations.leftPop(redisKey)redisOperations.getOperations().delete(redisKey)
Run:
.\mvnw.cmd test "-Dtest=TaskNo3Test"After completing all TODOs, run:
.\mvnw.cmd test