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