Java Notes
Build a complete fullstack application with Spring Boot backend and React frontend - Task Manager with authentication, real-time updates, and deployment.
Project Overview
The Spring Boot Fullstack Task Manager is a complete production-ready application with a Spring Boot 3 backend REST API and a React 18 frontend. It implements a Kanban-style task management system with user authentication (JWT), real-time notifications via WebSocket, team collaboration, deadline tracking, and cloud deployment. This project demonstrates how to build, integrate, and deploy a modern fullstack Java application.
This project is ideal for:
- Java developers wanting to build end-to-end applications
- Developers learning how to integrate React with Spring Boot
- Students preparing for fullstack Java developer interviews
- Anyone wanting to understand deployment pipelines (Docker, CI/CD)
The application supports project creation, task assignment with drag-and-drop Kanban boards, deadline notifications, team member management, file attachments, and activity logging.
Features
- User Registration & Login — Email-based signup with JWT token authentication
- Project Management — Create projects with name, description, deadline, and team members
- Kanban Board — Visual task board with columns: TODO, IN_PROGRESS, IN_REVIEW, DONE
- Task CRUD — Create tasks with title, description, priority, assignee, and deadline
- Drag & Drop — Move tasks between columns with React DnD (updates backend)
- Team Collaboration — Invite members to projects, assign tasks to team members
- Real-time Updates — WebSocket notifications when tasks are assigned or status changes
- Priority Levels — LOW, MEDIUM, HIGH, CRITICAL with color-coded badges
- Deadline Tracking — Visual indicators for approaching and overdue deadlines
- Task Comments — Discussion thread on each task with @mentions
- File Attachments — Upload documents and images to tasks (stored in S3)
- Activity Log — Complete audit trail of all project activities
- Dashboard — Personal dashboard with assigned tasks, upcoming deadlines, and stats
- Profile Management — Update profile picture, name, and notification preferences
- Search & Filter — Filter tasks by status, priority, assignee, and date range
- Email Notifications — Email alerts for task assignments and deadline reminders
- Dark Mode — Toggle between light and dark themes
- Responsive Design — Works on desktop, tablet, and mobile screens
- Role-Based Access — Project Owner, Admin, Member with different permissions
- Export — Export project data as CSV/PDF for reporting
Project Structure
Database Schema (PostgreSQL)
users Table
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | UUID | PRIMARY KEY | Unique user ID |
| VARCHAR(100) | UNIQUE, NOT NULL | Login email | |
| password | VARCHAR(255) | NOT NULL | BCrypt hash |
| full_name | VARCHAR(100) | NOT NULL | Display name |
| avatar_url | VARCHAR(255) | Profile picture | |
| created_at | TIMESTAMP | NOT NULL | Registration time |
projects Table
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | UUID | PRIMARY KEY | Unique project ID |
| name | VARCHAR(100) | NOT NULL | Project name |
| description | TEXT | Project description | |
| owner_id | UUID | FOREIGN KEY | Project creator |
| deadline | DATE | Project deadline | |
| status | VARCHAR(20) | DEFAULT 'ACTIVE' | ACTIVE, ARCHIVED |
| created_at | TIMESTAMP | NOT NULL | Creation time |
tasks Table
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | UUID | PRIMARY KEY | Unique task ID |
| title | VARCHAR(200) | NOT NULL | Task title |
| description | TEXT | Task details | |
| project_id | UUID | FOREIGN KEY | Parent project |
| assignee_id | UUID | FOREIGN KEY | Assigned member |
| reporter_id | UUID | FOREIGN KEY | Task creator |
| status | VARCHAR(20) | DEFAULT 'TODO' | TODO, IN_PROGRESS, IN_REVIEW, DONE |
| priority | VARCHAR(10) | DEFAULT 'MEDIUM' | LOW, MEDIUM, HIGH, CRITICAL |
| deadline | TIMESTAMP | Task deadline | |
| position | INT | Order within column | |
| created_at | TIMESTAMP | NOT NULL | Creation time |
| updated_at | TIMESTAMP | NOT NULL | Last update |
project_members Table
| Column | Type | Constraints | Description |
|---|---|---|---|
| project_id | UUID | FOREIGN KEY, PK | Project reference |
| user_id | UUID | FOREIGN KEY, PK | Member reference |
| role | VARCHAR(20) | NOT NULL | OWNER, ADMIN, MEMBER |
| joined_at | TIMESTAMP | NOT NULL | Join time |
Key Code Snippets
Task Service Implementation
package com.wohotech.taskmanager.service;
import com.wohotech.taskmanager.dto.request.TaskRequest;
import com.wohotech.taskmanager.dto.request.TaskStatusUpdateRequest;
import com.wohotech.taskmanager.dto.response.TaskResponse;
import com.wohotech.taskmanager.entity.*;
import com.wohotech.taskmanager.enums.Priority;
import com.wohotech.taskmanager.enums.TaskStatus;
import com.wohotech.taskmanager.exception.ResourceNotFoundException;
import com.wohotech.taskmanager.repository.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
@Service
@RequiredArgsConstructor
@Slf4j
public class TaskServiceImpl implements TaskService {
private final TaskRepository taskRepository;
private final ProjectRepository projectRepository;
private final UserRepository userRepository;
private final NotificationService notificationService;
private final ActivityLogRepository activityLogRepository;
@Override
@Transactional
public TaskResponse createTask(UUID projectId, TaskRequest request, UUID currentUserId) {
Project project = projectRepository.findById(projectId)
.orElseThrow(() -> new ResourceNotFoundException("Project not found"));
User reporter = userRepository.findById(currentUserId)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
// Get max position in the TODO column for ordering
int maxPosition = taskRepository
.findMaxPositionByProjectAndStatus(projectId, TaskStatus.TODO)
.orElse(0);
Task task = Task.builder()
.id(UUID.randomUUID())
.title(request.getTitle())
.description(request.getDescription())
.project(project)
.reporter(reporter)
.status(TaskStatus.TODO)
.priority(Priority.valueOf(request.getPriority()))
.deadline(request.getDeadline())
.position(maxPosition + 1)
.createdAt(LocalDateTime.now())
.updatedAt(LocalDateTime.now())
.build();
// Assign to member if specified
if (request.getAssigneeId() != null) {
User assignee = userRepository.findById(request.getAssigneeId())
.orElseThrow(() -> new ResourceNotFoundException("Assignee not found"));
task.setAssignee(assignee);
// Send real-time notification to assignee
notificationService.sendTaskAssignedNotification(assignee, task, reporter);
}
Task saved = taskRepository.save(task);
// Log activity
logActivity(project, currentUserId, "Created task: " + task.getTitle());
log.info("Task created: {} in project: {}", task.getTitle(), project.getName());
return mapToResponse(saved);
}
@Override
@Transactional
public TaskResponse updateTaskStatus(UUID taskId, TaskStatusUpdateRequest request,
UUID currentUserId) {
Task task = taskRepository.findById(taskId)
.orElseThrow(() -> new ResourceNotFoundException("Task not found"));
TaskStatus oldStatus = task.getStatus();
TaskStatus newStatus = TaskStatus.valueOf(request.getStatus());
task.setStatus(newStatus);
task.setPosition(request.getPosition());
task.setUpdatedAt(LocalDateTime.now());
Task updated = taskRepository.save(task);
// Notify assignee about status change
if (task.getAssignee() != null &&
!task.getAssignee().getId().equals(currentUserId)) {
notificationService.sendStatusChangeNotification(
task.getAssignee(), task, oldStatus, newStatus);
}
// Log activity
logActivity(task.getProject(), currentUserId,
"Moved \"" + task.getTitle() + "\" from " + oldStatus + " to " + newStatus);
return mapToResponse(updated);
}
@Override
@Transactional(readOnly = true)
public List<TaskResponse> getTasksByProject(UUID projectId) {
return taskRepository.findByProjectIdOrderByPositionAsc(projectId)
.stream()
.map(this::mapToResponse)
.toList();
}
@Override
@Transactional(readOnly = true)
public List<TaskResponse> getMyTasks(UUID userId) {
return taskRepository.findByAssigneeIdOrderByDeadlineAsc(userId)
.stream()
.map(this::mapToResponse)
.toList();
}
private void logActivity(Project project, UUID userId, String action) {
ActivityLog log = ActivityLog.builder()
.project(project)
.userId(userId)
.action(action)
.timestamp(LocalDateTime.now())
.build();
activityLogRepository.save(log);
}
private TaskResponse mapToResponse(Task task) {
return TaskResponse.builder()
.id(task.getId())
.title(task.getTitle())
.description(task.getDescription())
.status(task.getStatus().name())
.priority(task.getPriority().name())
.assignee(task.getAssignee() != null ?
task.getAssignee().getFullName() : null)
.assigneeAvatar(task.getAssignee() != null ?
task.getAssignee().getAvatarUrl() : null)
.deadline(task.getDeadline())
.position(task.getPosition())
.createdAt(task.getCreatedAt())
.build();
}
}React Kanban Board Component
CORS Configuration (Backend)
package com.wohotech.taskmanager.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration
public class CorsConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// Allow React dev server and production domain
configuration.setAllowedOrigins(List.of(
"http://localhost:5173", // Vite dev server
"http://localhost:3000", // Alternative
"https://taskmanager.wohotech.com" // Production
));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true); // Allow cookies/auth headers
configuration.setMaxAge(3600L); // Cache preflight for 1 hour
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration);
source.registerCorsConfiguration("/ws/**", configuration);
return source;
}
}Axios API Service (Frontend)
API Endpoints
| Method | URL | Description | Auth |
|---|---|---|---|
| POST | /api/v1/auth/register | Register user | No |
| POST | /api/v1/auth/login | Login (JWT) | No |
| POST | /api/v1/auth/refresh | Refresh token | Yes |
| GET | /api/v1/projects | List user's projects | Yes |
| POST | /api/v1/projects | Create project | Yes |
| GET | /api/v1/projects/{id} | Project details | Yes (Member) |
| PUT | /api/v1/projects/{id} | Update project | Yes (Owner/Admin) |
| POST | /api/v1/projects/{id}/members | Add member | Yes (Owner/Admin) |
| GET | /api/v1/projects/{id}/tasks | Get all tasks (for board) | Yes (Member) |
| POST | /api/v1/projects/{id}/tasks | Create task | Yes (Member) |
| PUT | /api/v1/tasks/{id} | Update task | Yes (Assignee/Admin) |
| PATCH | /api/v1/tasks/{id}/status | Update status (drag & drop) | Yes (Member) |
| DELETE | /api/v1/tasks/{id} | Delete task | Yes (Reporter/Admin) |
| POST | /api/v1/tasks/{id}/comments | Add comment | Yes (Member) |
| GET | /api/v1/dashboard | Personal dashboard | Yes |
| GET | /api/v1/notifications | Get notifications | Yes |
| PUT | /api/v1/notifications/read | Mark all read | Yes |
How to Run
Development Setup
Backend:
cd backend
# Configure database in application.yml
mvn clean install
mvn spring-boot:run
# Backend runs on http://localhost:8080Frontend:
cd frontend
npm install
npm run dev
# Frontend runs on http://localhost:5173Docker Compose (Full Stack)
docker-compose up --build
# Access at http://localhostScreenshots Description
- Login Page — Clean login form with Material UI, "Remember me" and OAuth buttons
- Dashboard — Personal task summary with assigned tasks, deadlines, and activity feed
- Kanban Board — 4-column board with colored task cards showing priority, assignee avatar
- Drag & Drop — Task being dragged from TODO to IN_PROGRESS with visual indicator
- Task Detail Modal — Full task view with description, comments thread, and attachments
- Project Settings — Team members list with role management and project settings
- Mobile View — Responsive board stacking columns vertically on phone screen
- Dark Mode — Full application in dark theme with proper contrast
Future Enhancements
- Sprint Planning — Scrum sprints with velocity tracking and burndown charts
- Time Tracking — Log hours spent on each task with timer widget
- GitHub Integration — Link tasks to PRs and auto-update status on merge
- Calendar View — See tasks by deadline in calendar format
- Gantt Chart — Project timeline visualization with dependencies
- Custom Fields — Add custom fields to tasks (story points, labels)
- Mobile App — React Native or Flutter companion app
- AI Task Suggestions — Auto-suggest task breakdowns from descriptions
- Templates — Reusable project templates (Software Dev, Marketing, etc.)
- Multi-tenant SaaS — Organization-level isolation for B2B deployment
Interview Questions
Q1: How do you handle CORS between React (port 5173) and Spring Boot (port 8080)?
Answer: In development, React runs on a different origin than Spring Boot. Without CORS configuration, the browser blocks cross-origin requests. We configure CorsConfigurationSource bean in Spring Security to allow the React origin, specify allowed methods (GET, POST, PUT, DELETE), allow all headers, and set allowCredentials(true) for JWT cookies. In production, Nginx serves both under the same domain (frontend at /, API at /api/), eliminating CORS entirely.
Q2: How does the drag-and-drop update work end-to-end?
Answer: (1) User drags a TaskCard (React DnD useDrag hook) and drops it on a TaskColumn (useDrop hook), (2) onDrop callback dispatches updateTaskStatus Redux thunk, (3) Thunk sends PATCH /api/v1/tasks/{id}/status with new status and position, (4) Backend updates the task, saves to DB, and publishes a WebSocket event, (5) Other team members viewing the same board receive the update via STOMP subscription and their Redux store updates in real-time, (6) Frontend uses optimistic updates — UI changes immediately, reverts on API failure.
Q3: How do you handle JWT refresh tokens?
Answer: On login, the server returns both accessToken (15 min expiry) and refreshToken (7 days). The Axios response interceptor catches 401 errors, automatically calls /auth/refresh with the refresh token, gets a new access token, stores it, and retries the original request transparently. If refresh fails (token expired/revoked), the user is redirected to login. Refresh tokens are stored in Redis for server-side revocation on logout.
Q4: How do you structure the React state with Redux Toolkit?
Answer: We use feature-based slices: authSlice (user info, tokens), projectSlice (project list, current project), taskSlice (tasks array, loading states). Each slice uses createAsyncThunk for API calls, which auto-generates pending/fulfilled/rejected actions. We use createEntityAdapter for normalized task storage (by ID). Selectors are co-located with slices. RTK Query could replace manual API service but we chose explicit thunks for learning clarity.
Q5: How do you deploy this fullstack app?
Answer: (1) GitHub Actions CI: runs tests, builds backend JAR and frontend static files, (2) Docker multi-stage builds: backend Dockerfile uses maven:3.9 for build + eclipse-temurin:17-jre for runtime; frontend uses node:20 for build + nginx:alpine for serving, (3) Docker Compose orchestrates all services, (4) Nginx reverse proxy: routes /api to backend container, /ws to WebSocket, and / to frontend. (5) Deploy to AWS EC2 with docker-compose, or Railway/Render for simpler PaaS deployment.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Spring Boot Fullstack Project.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, projects, springboot, fullstack
Related Java Master Course Topics