Online Voting System in Java

A comprehensive, secure, and scalable online voting system built with Java and Spring Boot.

System Architecture

Frontend (React/Thymeleaf) → Spring Boot Controllers → Services → Database ↑ ↑ ↑ ↑ Validation Security Business Persistence (Spring Security) Logic (JPA/Hibernate)

Project Structure

Maven Dependencies

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.1.0</version> </parent> <groupId>com.voting</groupId> <artifactId>online-voting-system</artifactId> <version>1.0.0</version> <dependencies> <!-- Spring Boot Starters --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- Database --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <!-- Security --> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity6</artifactId> </dependency> <!-- Utilities --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- Testing --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>

Domain Models

Entity Classes

package com.voting.system.models; import jakarta.persistence.*; import jakarta.validation.constraints.*; import java.time.LocalDateTime; import java.util.HashSet; import java.util.Set; @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank(message = "Username is required") @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters") @Column(unique = true, nullable = false) private String username; @NotBlank(message = "Email is required") @Email(message = "Email should be valid") @Column(unique = true, nullable = false) private String email; @NotBlank(message = "Password is required") @Size(min = 8, message = "Password must be at least 8 characters") private String password; @NotBlank(message = "Full name is required") private String fullName; @Pattern(regexp = "\\d{10}", message = "Phone number must be 10 digits") private String phoneNumber; private boolean enabled = true; @Enumerated(EnumType.STRING) private UserRole role = UserRole.VOTER; @OneToMany(mappedBy = "voter", cascade = CascadeType.ALL) private Set<Vote> votes = new HashSet<>(); private LocalDateTime createdAt; private LocalDateTime updatedAt; public enum UserRole { ADMIN, ELECTION_OFFICER, VOTER } // Pre-persist and pre-update methods @PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); } @PreUpdate protected void onUpdate() { updatedAt = LocalDateTime.now(); } // Constructors public User() {} public User(String username, String email, String password, String fullName, String phoneNumber) { this.username = username; this.email = email; this.password = password; this.fullName = fullName; this.phoneNumber = phoneNumber; } // Getters and Setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } public String getPhoneNumber() { return phoneNumber; } public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public UserRole getRole() { return role; } public void setRole(UserRole role) { this.role = role; } public Set<Vote> getVotes() { return votes; } public void setVotes(Set<Vote> votes) { this.votes = votes; } public LocalDateTime getCreatedAt() { return createdAt; } public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } public LocalDateTime getUpdatedAt() { return updatedAt; } public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } }
package com.voting.system.models; import jakarta.persistence.*; import jakarta.validation.constraints.*; import java.time.LocalDateTime; import java.util.HashSet; import java.util.Set; @Entity @Table(name = "elections") public class Election { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank(message = "Election title is required") @Size(max = 200, message = "Title cannot exceed 200 characters") private String title; @NotBlank(message = "Description is required") @Column(columnDefinition = "TEXT") private String description; @NotNull(message = "Start date is required") private LocalDateTime startDate; @NotNull(message = "End date is required") private LocalDateTime endDate; @Enumerated(EnumType.STRING) private ElectionStatus status = ElectionStatus.SCHEDULED; @OneToMany(mappedBy = "election", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private Set<Candidate> candidates = new HashSet<>(); @OneToMany(mappedBy = "election", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private Set<Vote> votes = new HashSet<>(); private LocalDateTime createdAt; private LocalDateTime updatedAt; public enum ElectionStatus { SCHEDULED, ONGOING, COMPLETED, CANCELLED } @PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); updateStatus(); } @PreUpdate protected void onUpdate() { updatedAt = LocalDateTime.now(); updateStatus(); } private void updateStatus() { LocalDateTime now = LocalDateTime.now(); if (now.isBefore(startDate)) { status = ElectionStatus.SCHEDULED; } else if (now.isAfter(startDate) && now.isBefore(endDate)) { status = ElectionStatus.ONGOING; } else if (now.isAfter(endDate)) { status = ElectionStatus.COMPLETED; } } // Business logic methods public boolean isVotingOpen() { LocalDateTime now = LocalDateTime.now(); return now.isAfter(startDate) && now.isBefore(endDate) && status == ElectionStatus.ONGOING; } public boolean hasUserVoted(User user) { return votes.stream().anyMatch(vote -> vote.getVoter().equals(user)); } // Constructors, getters, and setters public Election() {} public Election(String title, String description, LocalDateTime startDate, LocalDateTime endDate) { this.title = title; this.description = description; this.startDate = startDate; this.endDate = endDate; } // Getters and setters... public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public LocalDateTime getStartDate() { return startDate; } public void setStartDate(LocalDateTime startDate) { this.startDate = startDate; } public LocalDateTime getEndDate() { return endDate; } public void setEndDate(LocalDateTime endDate) { this.endDate = endDate; } public ElectionStatus getStatus() { return status; } public void setStatus(ElectionStatus status) { this.status = status; } public Set<Candidate> getCandidates() { return candidates; } public void setCandidates(Set<Candidate> candidates) { this.candidates = candidates; } public Set<Vote> getVotes() { return votes; } public void setVotes(Set<Vote> votes) { this.votes = votes; } public LocalDateTime getCreatedAt() { return createdAt; } public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } public LocalDateTime getUpdatedAt() { return updatedAt; } public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } }
package com.voting.system.models; import jakarta.persistence.*; import jakarta.validation.constraints.*; @Entity @Table(name = "candidates") public class Candidate { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank(message = "Candidate name is required") private String name; @NotBlank(message = "Party affiliation is required") private String party; @Column(columnDefinition = "TEXT") private String bio; private String photoUrl; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "election_id", nullable = false) private Election election; @OneToMany(mappedBy = "candidate", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private Set<Vote> votes = new HashSet<>(); private LocalDateTime createdAt; @PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); } // Business method public long getVoteCount() { return votes != null ? votes.size() : 0; } // Constructors, getters, and setters public Candidate() {} public Candidate(String name, String party, String bio, Election election) { this.name = name; this.party = party; this.bio = bio; this.election = election; } // Getters and setters... }
package com.voting.system.models; import jakarta.persistence.*; import java.time.LocalDateTime; @Entity @Table(name = "votes", uniqueConstraints = { @UniqueConstraint(columnNames = {"voter_id", "election_id"}) }) public class Vote { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "voter_id", nullable = false) private User voter; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "candidate_id", nullable = false) private Candidate candidate; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "election_id", nullable = false) private Election election; private LocalDateTime votedAt; @PrePersist protected void onCreate() { votedAt = LocalDateTime.now(); } // Constructors, getters, and setters public Vote() {} public Vote(User voter, Candidate candidate, Election election) { this.voter = voter; this.candidate = candidate; this.election = election; } // Getters and setters... }

Repository Layer

package com.voting.system.repositories; import com.voting.system.models.*; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @Repository public interface UserRepository extends JpaRepository<User, Long> { Optional<User> findByUsername(String username); Optional<User> findByEmail(String email); boolean existsByUsername(String username); boolean existsByEmail(String email); @Query("SELECT u FROM User u WHERE u.role = 'VOTER' AND u.enabled = true") List<User> findAllActiveVoters(); } @Repository public interface ElectionRepository extends JpaRepository<Election, Long> { List<Election> findByStatus(Election.ElectionStatus status); @Query("SELECT e FROM Election e WHERE e.startDate <= :now AND e.endDate >= :now") List<Election> findActiveElections(@Param("now") LocalDateTime now); @Query("SELECT e FROM Election e WHERE e.endDate < :now AND e.status = 'ONGOING'") List<Election> findElectionsToComplete(@Param("now") LocalDateTime now); @Query("SELECT COUNT(v) FROM Vote v WHERE v.election.id = :electionId AND v.voter.id = :voterId") long hasUserVotedInElection(@Param("electionId") Long electionId, @Param("voterId") Long voterId); } @Repository public interface CandidateRepository extends JpaRepository<Candidate, Long> { List<Candidate> findByElectionId(Long electionId); @Query("SELECT c FROM Candidate c WHERE c.election.id = :electionId ORDER BY c.name") List<Candidate> findCandidatesByElection(@Param("electionId") Long electionId); @Query("SELECT c, COUNT(v) as voteCount FROM Candidate c LEFT JOIN c.votes v " + "WHERE c.election.id = :electionId GROUP BY c ORDER BY voteCount DESC") List<Object[]> findCandidatesWithVoteCount(@Param("electionId") Long electionId); } @Repository public interface VoteRepository extends JpaRepository<Vote, Long> { boolean existsByVoterIdAndElectionId(Long voterId, Long electionId); @Query("SELECT COUNT(v) FROM Vote v WHERE v.election.id = :electionId") long countVotesByElection(@Param("electionId") Long electionId); @Query("SELECT v.candidate, COUNT(v) FROM Vote v WHERE v.election.id = :electionId GROUP BY v.candidate") List<Object[]> getVoteCountsByCandidate(@Param("electionId") Long electionId); }

Service Layer

package com.voting.system.services; import com.voting.system.models.*; import com.voting.system.repositories.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @Service @Transactional public class UserService { @Autowired private UserRepository userRepository; @Autowired private PasswordEncoder passwordEncoder; public User registerUser(User user) { if (userRepository.existsByUsername(user.getUsername())) { throw new IllegalArgumentException("Username already exists"); } if (userRepository.existsByEmail(user.getEmail())) { throw new IllegalArgumentException("Email already exists"); } user.setPassword(passwordEncoder.encode(user.getPassword())); return userRepository.save(user); } public Optional<User> findByUsername(String username) { return userRepository.findByUsername(username); } public List<User> getAllVoters() { return userRepository.findAllActiveVoters(); } public User updateUserProfile(Long userId, User updatedUser) { User existingUser = userRepository.findById(userId) .orElseThrow(() -> new IllegalArgumentException("User not found")); if (!existingUser.getEmail().equals(updatedUser.getEmail()) && userRepository.existsByEmail(updatedUser.getEmail())) { throw new IllegalArgumentException("Email already exists"); } existingUser.setFullName(updatedUser.getFullName()); existingUser.setEmail(updatedUser.getEmail()); existingUser.setPhoneNumber(updatedUser.getPhoneNumber()); return userRepository.save(existingUser); } } @Service @Transactional public class ElectionService { @Autowired private ElectionRepository electionRepository; @Autowired private CandidateRepository candidateRepository; @Autowired private VoteRepository voteRepository; public Election createElection(Election election) { if (election.getEndDate().isBefore(election.getStartDate())) { throw new IllegalArgumentException("End date cannot be before start date"); } if (election.getStartDate().isBefore(LocalDateTime.now())) { throw new IllegalArgumentException("Start date cannot be in the past"); } return electionRepository.save(election); } public List<Election> getActiveElections() { return electionRepository.findActiveElections(LocalDateTime.now()); } public List<Election> getElectionsByStatus(Election.ElectionStatus status) { return electionRepository.findByStatus(status); } @Cacheable("electionResults") public Map<Candidate, Long> getElectionResults(Long electionId) { Election election = electionRepository.findById(electionId) .orElseThrow(() -> new IllegalArgumentException("Election not found")); if (election.getStatus() != Election.ElectionStatus.COMPLETED) { throw new IllegalStateException("Election results are not available yet"); } return voteRepository.getVoteCountsByCandidate(electionId).stream() .collect(Collectors.toMap( result -> (Candidate) result[0], result -> (Long) result[1] )); } @Scheduled(fixedRate = 60000) // Run every minute public void updateElectionStatuses() { LocalDateTime now = LocalDateTime.now(); List<Election> electionsToUpdate = electionRepository.findElectionsToComplete(now); for (Election election : electionsToUpdate) { election.setStatus(Election.ElectionStatus.COMPLETED); electionRepository.save(election); } } } @Service @Transactional public class VotingService { @Autowired private VoteRepository voteRepository; @Autowired private ElectionRepository electionRepository; @Autowired private CandidateRepository candidateRepository; @Autowired private UserRepository userRepository; public Vote castVote(Long voterId, Long electionId, Long candidateId) { // Check if election is active Election election = electionRepository.findById(electionId) .orElseThrow(() -> new IllegalArgumentException("Election not found")); if (!election.isVotingOpen()) { throw new IllegalStateException("Voting is not open for this election"); } // Check if user has already voted if (voteRepository.existsByVoterIdAndElectionId(voterId, electionId)) { throw new IllegalStateException("You have already voted in this election"); } // Verify candidate exists in this election Candidate candidate = candidateRepository.findById(candidateId) .orElseThrow(() -> new IllegalArgumentException("Candidate not found")); if (!candidate.getElection().getId().equals(electionId)) { throw new IllegalArgumentException("Candidate does not belong to this election"); } // Verify voter exists and is enabled User voter = userRepository.findById(voterId) .orElseThrow(() -> new IllegalArgumentException("Voter not found")); if (!voter.isEnabled()) { throw new IllegalStateException("Voter account is disabled"); } // Create and save vote Vote vote = new Vote(voter, candidate, election); return voteRepository.save(vote); } public boolean hasUserVoted(Long voterId, Long electionId) { return voteRepository.existsByVoterIdAndElectionId(voterId, electionId); } }

Security Configuration

package com.voting.system.config; import com.voting.system.services.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private UserService userService; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz -> authz .requestMatchers("/", "/home", "/register", "/css/**", "/js/**", "/images/**").permitAll() .requestMatchers("/admin/**").hasRole("ADMIN") .requestMatchers("/election-officer/**").hasAnyRole("ADMIN", "ELECTION_OFFICER") .requestMatchers("/vote/**", "/profile/**").hasRole("VOTER") .anyRequest().authenticated() ) .formLogin(form -> form .loginPage("/login") .defaultSuccessUrl("/dashboard", true) .permitAll() ) .logout(logout -> logout .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .logoutSuccessUrl("/login?logout") .permitAll() ) .exceptionHandling(exceptions -> exceptions .accessDeniedPage("/access-denied") ); return http.build(); } }

REST Controllers

package com.voting.system.controllers; import com.voting.system.models.*; import com.voting.system.services.*; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.List; import java.util.Map; @Controller @RequestMapping("/api") public class VotingController { @Autowired private ElectionService electionService; @Autowired private VotingService votingService; @Autowired private UserService userService; // REST Endpoints for mobile apps or external systems @GetMapping("/elections/active") public ResponseEntity<List<Election>> getActiveElections() { List<Election> activeElections = electionService.getActiveElections(); return ResponseEntity.ok(activeElections); } @PostMapping("/vote") public ResponseEntity<?> castVote(@RequestBody VoteRequest voteRequest, Authentication authentication) { try { User user = (User) authentication.getPrincipal(); Vote vote = votingService.castVote(user.getId(), voteRequest.getElectionId(), voteRequest.getCandidateId()); Map<String, String> response = new HashMap<>(); response.put("message", "Vote cast successfully"); response.put("voteId", vote.getId().toString()); return ResponseEntity.ok(response); } catch (Exception e) { Map<String, String> error = new HashMap<>(); error.put("error", e.getMessage()); return ResponseEntity.badRequest().body(error); } } @GetMapping("/elections/{electionId}/results") public ResponseEntity<?> getElectionResults(@PathVariable Long electionId) { try { Map<Candidate, Long> results = electionService.getElectionResults(electionId); return ResponseEntity.ok(results); } catch (Exception e) { Map<String, String> error = new HashMap<>(); error.put("error", e.getMessage()); return ResponseEntity.badRequest().body(error); } } public static class VoteRequest { private Long electionId; private Long candidateId; // Getters and setters public Long getElectionId() { return electionId; } public void setElectionId(Long electionId) { this.electionId = electionId; } public Long getCandidateId() { return candidateId; } public void setCandidateId(Long candidateId) { this.candidateId = candidateId; } } } @Controller public class WebController { @Autowired private ElectionService electionService; @Autowired private VotingService votingService; @GetMapping("/") public String home() { return "home"; } @GetMapping("/login") public String login() { return "login"; } @GetMapping("/dashboard") public String dashboard(Authentication authentication, Model model) { User user = (User) authentication.getPrincipal(); model.addAttribute("user", user); List<Election> activeElections = electionService.getActiveElections(); model.addAttribute("activeElections", activeElections); return "dashboard"; } @GetMapping("/vote/{electionId}") public String votePage(@PathVariable Long electionId, Authentication authentication, Model model) { User user = (User) authentication.getPrincipal(); Election election = electionService.getElectionById(electionId) .orElseThrow(() -> new IllegalArgumentException("Election not found")); if (votingService.hasUserVoted(user.getId(), electionId)) { model.addAttribute("error", "You have already voted in this election"); return "redirect:/dashboard"; } if (!election.isVotingOpen()) { model.addAttribute("error", "Voting is not open for this election"); return "redirect:/dashboard"; } model.addAttribute("election", election); model.addAttribute("candidates", election.getCandidates()); return "vote"; } @PostMapping("/vote/{electionId}") public String castVote(@PathVariable Long electionId, @RequestParam Long candidateId, Authentication authentication, Model model) { try { User user = (User) authentication.getPrincipal(); votingService.castVote(user.getId(), electionId, candidateId); model.addAttribute("success", "Your vote has been cast successfully!"); } catch (Exception e) { model.addAttribute("error", e.getMessage()); } return "redirect:/dashboard"; } }

Configuration and Main Application

package com.voting.system; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableCaching @EnableScheduling public class OnlineVotingSystemApplication { public static void main(String[] args) { SpringApplication.run(OnlineVotingSystemApplication.class, args); } }
# application.yml spring: datasource: url: jdbc:postgresql://localhost:5432/voting_system username: voting_admin password: secure_password driver-class-name: org.postgresql.Driver jpa: hibernate: ddl-auto: update properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true show-sql: false cache: type: redis redis: time-to-live: 60000 thymeleaf: cache: false server: port: 8080 logging: level: com.voting.system: DEBUG org.springframework.security: INFO app: voting: timezone: UTC results-delay-hours: 1

Testing

package com.voting.system.services; import com.voting.system.models.*; import com.voting.system.repositories.*; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.time.LocalDateTime; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class VotingServiceTest { @Mock private VoteRepository voteRepository; @Mock private ElectionRepository electionRepository; @Mock private CandidateRepository candidateRepository; @Mock private UserRepository userRepository; @InjectMocks private VotingService votingService; @Test public void testCastVote_Success() { // Setup User voter = new User(); voter.setId(1L); voter.setEnabled(true); Election election = new Election(); election.setId(1L); election.setStartDate(LocalDateTime.now().minusHours(1)); election.setEndDate(LocalDateTime.now().plusHours(1)); Candidate candidate = new Candidate(); candidate.setId(1L); candidate.setElection(election); when(electionRepository.findById(1L)).thenReturn(Optional.of(election)); when(voteRepository.existsByVoterIdAndElectionId(1L, 1L)).thenReturn(false); when(candidateRepository.findById(1L)).thenReturn(Optional.of(candidate)); when(userRepository.findById(1L)).thenReturn(Optional.of(voter)); when(voteRepository.save(any(Vote.class))).thenReturn(new Vote()); // Execute Vote result = votingService.castVote(1L, 1L, 1L); // Verify assertNotNull(result); verify(voteRepository, times(1)).save(any(Vote.class)); } @Test public void testCastVote_AlreadyVoted() { // Setup Election election = new Election(); election.setId(1L); election.setStartDate(LocalDateTime.now().minusHours(1)); election.setEndDate(LocalDateTime.now().plusHours(1)); when(electionRepository.findById(1L)).thenReturn(Optional.of(election)); when(voteRepository.existsByVoterIdAndElectionId(1L, 1L)).thenReturn(true); // Execute & Verify assertThrows(IllegalStateException.class, () -> { votingService.castVote(1L, 1L, 1L); }); } }

Key Features

Security Features

  1. Authentication & Authorization - Role-based access control
  2. Password Encryption - BCrypt password hashing
  3. CSRF Protection - Built-in Spring Security protection
  4. Session Management - Secure session handling

Voting Features

  1. Real-time Election Status - Automatic status updates
  2. One Vote Per User - Prevents duplicate voting
  3. Voting Time Windows - Elections have specific time frames
  4. Results Calculation - Automatic vote counting and results

Administrative Features

  1. Election Management - Create, update, and manage elections
  2. Candidate Management - Add and manage candidates
  3. User Management - Manage voter accounts
  4. Real-time Monitoring - Monitor election progress

Technical Features

  1. Caching - Redis caching for performance
  2. Scheduling - Automated tasks (status updates, etc.)
  3. REST API - Support for mobile and external applications
  4. Database Persistence - PostgreSQL with JPA/Hibernate

This online voting system provides a secure, scalable, and feature-rich platform for conducting elections with proper authentication, authorization, and audit trails.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper