๐ณ๏ธ Building a Polling System in Java with Repository Pattern (In-Memory)
Table of Contents
๐ Introduction
Polls and voting systems are everywhere โ from online classrooms to event feedback apps.
In this blog, weโll design and implement a Java-based Polling System that follows the Repository Pattern with an in-memory store (no database).
This project is a great way to practice:
- Java OOP principles
- Layered architecture & separation of concerns
- Defensive programming & validations
โ Problem Statement
We want to design a polling platform where:
- Admins can create polls with multiple options.
- Users can vote on polls.
- Results can be fetched per poll.
Constraints:
- A poll must have at least 2 unique, non-empty options.
- Options should be case-insensitive (
Yes
vsyes
โ same). - Only admins can create polls.
โ System Requirements
- Create Polls (Admin only)
- View Polls (per Admin, or all)
- Record Votes (User action)
- View Results (tally votes)
๐๏ธ Design Approach
๐น Repository Pattern
Weโll use the Repository Pattern to separate persistence from business logic.
Repositories:
- UserRepository โ manages users
- PollRepository โ manages polls
- VoteRepository โ manages votes
Currently, they use in-memory storage (ConcurrentHashMap/List), but can later be swapped with a database.
๐น Entities
-
User โ
id
,name
,type
(ADMIN / USER) -
Poll โ
id
,question
,options
,createdBy
-
Vote โ
pollId
,userId
,selectedOption
๐๏ธ ER Diagram
If we extend to a database, the schema looks like this:
โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
โ USERS โ โ POLLS โ โ VOTES โ
โโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโค
โ user_id (PK) โโโโโโโโโโค created_by (FK)โ โ vote_id (PK)โ
โ name โ โ poll_id (PK) โโโโโโโโบโ poll_id (FK)โ
โ type (ADMIN/USER)โ โ question โ โ user_id (FK)โ
โโโโโโโโโโโโโโโโโ โ options[] โ โ option โ
โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
How Your Final Code Works
The final implementation of the polling system follows the Repository Pattern using in-memory storage. This design ensures a clean separation of concerns between controllers, repositories, and models.
Execution Flow
- User Creation
-
UserController
callsUserRepository.addUser(user)
. - Users are stored in-memory inside a
Map<Integer, User>
.
- Poll Creation
-
PollController
invokesPollRepository.addPoll(poll)
. - Polls are maintained in-memory inside a
Map<Integer, Poll>
.
- Voting
-
When
PollController.vote(userId, pollId, option)
is called:- The system retrieves the
User
fromUserRepository
. - Retrieves the
Poll
fromPollRepository
. - Creates a
Vote
object and adds it to the poll. - Prevents duplicate votes from the same user.
- The system retrieves the
- Fetching Results
-
PollController.getResults(pollId)
aggregates votes perPollOption
. - Returns results as a map of
Option โ Count
.
Final Code Walkthrough (In-Memory Repository)
// org/example/repository/UserRepository.java
package org.example.repository;
import org.example.models.User;
import java.util.*;
public class UserRepository {
private final Map<Integer, User> users = new HashMap<>();
public void addUser(User user) {
users.put(user.getId(), user);
}
public User getUser(int id) {
return users.get(id);
}
}
// org/example/repository/PollRepository.java
package org.example.repository;
import org.example.models.Poll;
import java.util.*;
public class PollRepository {
private final Map<Integer, Poll> polls = new HashMap<>();
public void addPoll(Poll poll) {
polls.put(poll.getId(), poll);
}
public Poll getPoll(int id) {
return polls.get(id);
}
}
// org/example/controllers/UserController.java
package org.example.controllers;
import org.example.models.User;
import org.example.enums.UserType;
import org.example.repository.UserRepository;
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void createUser(int id, String name, UserType type) {
User user = new User(id, name, type);
userRepository.addUser(user);
}
}
// org/example/controllers/PollController.java
package org.example.controllers;
import org.example.enums.PollOption;
import org.example.models.*;
import org.example.repository.*;
import java.util.*;
public class PollController {
private final PollRepository pollRepository;
private final UserRepository userRepository;
public PollController(PollRepository pollRepository, UserRepository userRepository) {
this.pollRepository = pollRepository;
this.userRepository = userRepository;
}
public void createPoll(int id, String question, List<PollOption> options) {
Poll poll = new Poll(id, question, options);
pollRepository.addPoll(poll);
}
public void vote(int userId, int pollId, PollOption option) {
User user = userRepository.getUser(userId);
Poll poll = pollRepository.getPoll(pollId);
if (user != null && poll != null) {
Vote vote = new Vote(user, option);
poll.addVote(vote);
}
}
public Map<PollOption, Long> getResults(int pollId) {
Poll poll = pollRepository.getPoll(pollId);
return poll.getVotes().stream()
.collect(Collectors.groupingBy(Vote::getOption, Collectors.counting()));
}
}
๐ป Implementation
Weโll keep a layered structure:
Main โ Controller โ Service โ Repository โ Models
๐ Enums
UserType.java
package org.example.polling.enums;
public enum UserType {
ADMIN,
USER
}
PollOption.java
package org.example.polling.enums;
public enum PollOption {
OPTION_A,
OPTION_B,
OPTION_C,
OPTION_D
}
๐ Models
User.java
package org.example.polling.entities;
import org.example.polling.enums.UserType;
public class User {
private final int userId;
private final String name;
private final UserType userType;
public User(int userId, String name, UserType userType) {
this.userId = userId;
this.name = name;
this.userType = userType;
}
public int getUserId() { return userId; }
public String getName() { return name; }
public UserType getUserType() { return userType; }
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", name='" + name + '\'' +
", userType=" + userType +
'}';
}
}
Poll.java
package org.example.polling.entities;
import org.example.polling.enums.PollOption;
import java.time.LocalDateTime;
import java.util.Map;
public class Poll {
private final int pollId;
private final String question;
private final Map<PollOption, String> options;
private final User createdBy;
private final LocalDateTime createdAt;
public Poll(int pollId, String question, Map<PollOption, String> options, User createdBy) {
this.pollId = pollId;
this.question = question;
this.options = options;
this.createdBy = createdBy;
this.createdAt = LocalDateTime.now();
}
public int getPollId() { return pollId; }
public String getQuestion() { return question; }
public Map<PollOption, String> getOptions() { return options; }
public User getCreatedBy() { return createdBy; }
public LocalDateTime getCreatedAt() { return createdAt; }
}
Vote.java
package org.example.polling.entities;
import org.example.polling.enums.PollOption;
public class Vote {
private final int pollId;
private final int userId;
private final PollOption selectedOption;
public Vote(int pollId, int userId, PollOption selectedOption) {
this.pollId = pollId;
this.userId = userId;
this.selectedOption = selectedOption;
}
public int getPollId() { return pollId; }
public int getUserId() { return userId; }
public PollOption getSelectedOption() { return selectedOption; }
}
๐ Exceptions
package org.example.polling.exceptions;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) { super(message); }
}
public class PollNotFoundException extends RuntimeException {
public PollNotFoundException(String message) { super(message); }
}
public class UnauthorizedActionException extends RuntimeException {
public UnauthorizedActionException(String message) { super(message); }
}
public class InvalidOptionException extends RuntimeException {
public InvalidOptionException(String message) { super(message); }
}
public class DuplicateVoteException extends RuntimeException {
public DuplicateVoteException(String message) { super(message); }
}
๐ผ Back to Top
๐ Repositories
UserRepository.java
package org.example.polling.repositories;
import org.example.polling.entities.User;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class UserRepository {
private final Map<Integer, User> users = new ConcurrentHashMap<>();
public void save(User user) { users.put(user.getUserId(), user); }
public User findById(int userId) { return users.get(userId); }
public boolean exists(int userId) { return users.containsKey(userId); }
}
PollRepository.java
package org.example.polling.repositories;
import org.example.polling.entities.Poll;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class PollRepository {
private final Map<Integer, Poll> polls = new ConcurrentHashMap<>();
private final AtomicInteger pollCounter = new AtomicInteger(1);
public Poll save(Poll poll) {
polls.put(poll.getPollId(), poll);
return poll;
}
public Poll findById(int pollId) { return polls.get(pollId); }
public int generatePollId() { return pollCounter.getAndIncrement(); }
}
VoteRepository.java
package org.example.polling.repositories;
import org.example.polling.entities.Vote;
import org.example.polling.enums.PollOption;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class VoteRepository {
private final Map<Integer, Map<PollOption, AtomicInteger>> pollResults = new ConcurrentHashMap<>();
private final Map<Integer, Map<Integer, PollOption>> userVotes = new ConcurrentHashMap<>();
public void saveVote(Vote vote) {
pollResults.putIfAbsent(vote.getPollId(), new ConcurrentHashMap<>());
userVotes.putIfAbsent(vote.getPollId(), new ConcurrentHashMap<>());
if (userVotes.get(vote.getPollId()).containsKey(vote.getUserId())) {
throw new RuntimeException("User already voted in this poll!");
}
userVotes.get(vote.getPollId()).put(vote.getUserId(), vote.getSelectedOption());
pollResults.get(vote.getPollId())
.computeIfAbsent(vote.getSelectedOption(), k -> new AtomicInteger(0))
.incrementAndGet();
}
public Map<PollOption, Integer> getResults(int pollId) {
Map<PollOption, Integer> results = new ConcurrentHashMap<>();
pollResults.getOrDefault(pollId, Map.of())
.forEach((opt, count) -> results.put(opt, count.get()));
return results;
}
public PollOption getUserVote(int pollId, int userId) {
return userVotes.getOrDefault(pollId, Map.of()).get(userId);
}
}
๐ Services
PollService.java
package org.example.polling.services;
import org.example.polling.entities.Poll;
import org.example.polling.entities.User;
import org.example.polling.enums.PollOption;
import org.example.polling.enums.UserType;
import org.example.polling.exceptions.InvalidOptionException;
import org.example.polling.exceptions.PollNotFoundException;
import org.example.polling.exceptions.UnauthorizedActionException;
import org.example.polling.repositories.PollRepository;
import org.example.polling.repositories.UserRepository;
import org.example.polling.repositories.VoteRepository;
import java.util.*;
public class PollService {
private final PollRepository pollRepository;
private final VoteRepository voteRepository;
private final UserRepository userRepository;
public PollService(PollRepository pollRepository, VoteRepository voteRepository, UserRepository userRepository) {
this.pollRepository = pollRepository;
this.voteRepository = voteRepository;
this.userRepository = userRepository;
}
public Poll createPoll(List<String> options, String question, User creator) {
if (creator.getUserType() != UserType.ADMIN) {
throw new UnauthorizedActionException("Only admins can create polls!");
}
Set<String> normalizedOptions = new LinkedHashSet<>();
for (String option : options) {
normalizedOptions.add(option.trim().toLowerCase());
}
if (normalizedOptions.size() < 2) {
throw new InvalidOptionException("At least two unique options required!");
}
Map<PollOption, String> optionMap = new EnumMap<>(PollOption.class);
int index = 0;
for (String opt : normalizedOptions) {
optionMap.put(PollOption.values()[index++], opt);
if (index >= PollOption.values().length) break;
}
int pollId = pollRepository.generatePollId();
Poll poll = new Poll(pollId, question, optionMap, creator);
return pollRepository.save(poll);
}
public Map<PollOption, Integer> getResults(int pollId) {
Poll poll = pollRepository.findById(pollId);
if (poll == null) throw new PollNotFoundException("Poll not found!");
return voteRepository.getResults(pollId);
}
}
UserService.java
package org.example.polling.services;
import org.example.polling.entities.User;
import org.example.polling.entities.Vote;
import org.example.polling.enums.PollOption;
import org.example.polling.exceptions.DuplicateVoteException;
import org.example.polling.exceptions.PollNotFoundException;
import org.example.polling.exceptions.UserNotFoundException;
import org.example.polling.repositories.PollRepository;
import org.example.polling.repositories.UserRepository;
import org.example.polling.repositories.VoteRepository;
public class UserService {
private final PollRepository pollRepository;
private final VoteRepository voteRepository;
private final UserRepository userRepository;
public UserService(PollRepository pollRepository, VoteRepository voteRepository, UserRepository userRepository) {
this.pollRepository = pollRepository;
this.voteRepository = voteRepository;
this.userRepository = userRepository;
}
public void registerUser(User user) { userRepository.save(user); }
public void castVote(Vote vote) {
if (!userRepository.exists(vote.getUserId())) throw new UserNotFoundException("User does not exist!");
if (pollRepository.findById(vote.getPollId()) == null) throw new PollNotFoundException("Poll does not exist!");
if (voteRepository.getUserVote(vote.getPollId(), vote.getUserId()) != null)
throw new DuplicateVoteException("User has already voted!");
voteRepository.saveVote(vote);
}
public PollOption getUserVoteForPoll(int userId, int pollId) {
return voteRepository.getUserVote(pollId, userId);
}
}
๐ Controllers
PollController.java
package org.example.polling.controllers;
import org.example.polling.entities.Poll;
import org.example.polling.entities.User;
import org.example.polling.enums.PollOption;
import org.example.polling.services.PollService;
import java.util.List;
import java.util.Map;
public class PollController {
private final PollService pollService;
public PollController(PollService pollService) { this.pollService = pollService; }
public Poll createPoll(List<String> options, String question, User creator) {
return pollService.createPoll(options, question, creator);
}
public Map<PollOption, Integer> getPollResults(int pollId) {
return pollService.getResults(pollId);
}
}
UserController.java
package org.example.polling.controllers;
import org.example.polling.entities.Vote;
import org.example.polling.enums.PollOption;
import org.example.polling.services.UserService;
public class UserController {
private final UserService userService;
public UserController(UserService userService) { this.userService = userService; }
public void castVote(Vote vote) { userService.castVote(vote); }
public PollOption getUserVote(int userId, int pollId) {
return userService.getUserVoteForPoll(userId, pollId);
}
}
๐ Main Runner
Main.java
package org.example.polling;
import org.example.polling.controllers.PollController;
import org.example.polling.controllers.UserController;
import org.example.polling.entities.Poll;
import org.example.polling.entities.User;
import org.example.polling.entities.Vote;
import org.example.polling.enums.PollOption;
import org.example.polling.enums.UserType;
import org.example.polling.repositories.PollRepository;
import org.example.polling.repositories.UserRepository;
import org.example.polling.repositories.VoteRepository;
import org.example.polling.services.PollService;
import org.example.polling.services.UserService;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class Main {
public static void main(String[] args) {
PollRepository pollRepository = new PollRepository();
VoteRepository voteRepository = new VoteRepository();
UserRepository userRepository = new UserRepository();
PollService pollService = new PollService(pollRepository, voteRepository, userRepository);
UserService userService = new UserService(pollRepository, voteRepository, userRepository);
PollController pollController = new PollController(pollService);
UserController userController = new UserController(userService);
// Create admin
User admin = new User(1001, "Admin", UserType.ADMIN);
userService.registerUser(admin);
// Create poll
List<String> options = Arrays.asList("Apple", "Mango", "Banana", "Grapes");
Poll poll = pollController.createPoll(options, "Which is your favorite option?", admin);
// Register users
User user1 = new User(1, "Alice", UserType.USER);
User user2 = new User(2, "Bob", UserType.USER);
User user3 = new User(3, "Charlie", UserType.USER);
User user4 = new User(4, "Diana", UserType.USER);
userService.registerUser(user1);
userService.registerUser(user2);
userService.registerUser(user3);
userService.registerUser(user4);
// Voting
userController.castVote(new Vote(poll.getPollId(), user1.getUserId(), PollOption.OPTION_A));
userController.castVote(new Vote(poll.getPollId(), user2.getUserId(), PollOption.OPTION_B));
userController.castVote(new Vote(poll.getPollId(), user3.getUserId(), PollOption.OPTION_A));
userController.castVote(new Vote(poll.getPollId(), user4.getUserId(), PollOption.OPTION_C));
// Results
System.out.println("Poll Results for Poll ID " + poll.getPollId() + ":");
Map<PollOption, Integer> results = pollController.getPollResults(poll.getPollId());
results.forEach((opt, count) -> System.out.println(opt + ": " + count));
// User check
System.out.println("\nUser 1 voted: " + userService.getUserVoteForPoll(user1.getUserId(), poll.getPollId()));
}
}
โถ๏ธ Sample Execution
Output after running Main.java
:
Poll Results for Poll ID 1:
OPTION_A: 2
OPTION_B: 1
OPTION_C: 1
User 1 voted: OPTION_A
๐ Future Improvements
- Replace in-memory repositories with a database (JPA/Hibernate).
- Add
More Details:
Get all articles related to system design
Hastag: SystemDesignWithZeeshanAli
Git: https://github.com/ZeeshanAli-0704/SystemDesignWithZeeshanAli
Top comments (0)