Java Project Ideas
Five Java project ideas with full descriptions, skills practiced, tech stack recommendations, and difficulty ratings to build your portfolio.
Project 1 — Personal Finance Tracker
Difficulty: Beginner | Estimated time: 2–3 weeks
What You Build
A command-line (and optionally REST) application to track income and expenses. Users can log transactions, categorise them, view summaries by month, and export reports as CSV.
Features
- Add/remove transactions with amount, category, date, and description
- View monthly summary: total income, total expenses, net balance
- Filter transactions by category, date range, or keyword
- Export filtered results to CSV
- Budget alerts when spending in a category exceeds a set limit
- Persist data to a local JSON file (beginner) or SQLite/H2 (intermediate)
Tech Stack
Core Java 17+
├── Jackson — JSON serialisation/deserialisation
├── JUnit 5 + Mockito — unit testing
├── H2 (optional) — embedded database
└── PicoCLI — command-line argument parsing
Sample Domain Model
public record Transaction(
UUID id,
String description,
BigDecimal amount,
Category category,
LocalDate date,
Type type // INCOME or EXPENSE
) {}
public enum Category { FOOD, TRANSPORT, UTILITIES, ENTERTAINMENT, SALARY, OTHER }
public class FinanceTracker {
private final List<Transaction> transactions = new ArrayList<>();
public void add(Transaction t) { transactions.add(t); }
public MonthlySummary summarise(YearMonth month) {
var monthTxns = transactions.stream()
.filter(t -> YearMonth.from(t.date()).equals(month))
.toList();
BigDecimal income = monthTxns.stream()
.filter(t -> t.type() == Type.INCOME)
.map(Transaction::amount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal expense = monthTxns.stream()
.filter(t -> t.type() == Type.EXPENSE)
.map(Transaction::amount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return new MonthlySummary(month, income, expense);
}
}
Skills You Practice
- Records, enums, and sealed interfaces for domain modelling
- Java Streams for filtering and aggregation
- File I/O and JSON with Jackson
- Unit testing with JUnit 5
- BigDecimal for monetary arithmetic (never use double for money)
Project 2 — URL Shortener Service
Difficulty: Intermediate | Estimated time: 3–4 weeks
What You Build
A REST API (Spring Boot) that shortens long URLs, redirects short codes to the original URL, tracks click statistics, and optionally enforces expiry.
Features
POST /shorten— accept a long URL, return a short code (e.g.,https://sho.rt/aB3kZ)GET /{code}— redirect to the original URL (HTTP 302)GET /{code}/stats— return click count, creation date, last accessed date- Custom alias support —
POST /shortenwith{"url": "...", "alias": "my-link"} - Expiry — short links can have a TTL; expired links return 410 Gone
- Rate limiting — prevent abuse of the shorten endpoint
Tech Stack
Spring Boot 3
├── Spring Web (REST controllers)
├── Spring Data JPA + PostgreSQL (persistence)
├── Spring Cache + Redis (cache recent redirects)
├── Flyway (database migrations)
├── JUnit 5 + Mockito + TestContainers (testing)
└── Docker Compose (local dev environment)
Sample API
@RestController
@RequestMapping("/api")
public class UrlShortenerController {
private final UrlShortenerService service;
@PostMapping("/shorten")
public ResponseEntity<ShortenResponse> shorten(@Valid @RequestBody ShortenRequest req) {
ShortUrl shortUrl = service.shorten(req.longUrl(), req.alias(), req.ttlDays());
return ResponseEntity.status(HttpStatus.CREATED)
.body(new ShortenResponse(shortUrl.code(), shortUrl.shortUrl()));
}
@GetMapping("/{code}")
public ResponseEntity<Void> redirect(@PathVariable String code) {
return service.resolve(code)
.map(url -> ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create(url.longUrl()))
.<Void>build())
.orElse(ResponseEntity.notFound().build());
}
@GetMapping("/{code}/stats")
public ResponseEntity<StatsResponse> stats(@PathVariable String code) {
return service.getStats(code)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}
Skills You Practice
- Spring Boot REST API design
- Spring Data JPA (entities, repositories, custom queries)
- Caching with
@Cacheableand Redis - Database migrations with Flyway
- Integration testing with TestContainers
- HTTP redirects, status codes, and caching headers
Project 3 — Real-Time Chat Application
Difficulty: Intermediate–Advanced | Estimated time: 4–6 weeks
What You Build
A WebSocket-based chat application with rooms, user authentication, message history, and real-time delivery. Includes a simple HTML/JS front end.
Features
- Create and join chat rooms
- Send and receive messages in real time via WebSocket
- Message history — last 50 messages loaded on join
- User presence — show who is online in a room
- JWT authentication — only authenticated users can connect
- Persistent message storage in PostgreSQL
- Read receipts and typing indicators
Tech Stack
Spring Boot 3
├── Spring WebSocket (STOMP over WebSocket)
├── Spring Security (JWT authentication)
├── Spring Data JPA + PostgreSQL
├── Project Loom virtual threads (Java 21)
└── Simple HTML + JS front end (or React if preferred)
Sample WebSocket Config
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*")
.withSockJS();
}
}
@Controller
public class ChatController {
@MessageMapping("/chat.send/{roomId}")
@SendTo("/topic/room/{roomId}")
public ChatMessage sendMessage(@DestinationVariable String roomId,
@Payload SendMessageRequest req,
Principal principal) {
return messageService.save(roomId, principal.getName(), req.content());
}
}
Skills You Practice
- WebSocket programming with STOMP
- JWT authentication and Spring Security
- Virtual threads for high concurrency
- Event-driven architecture
- Front-end integration (basic JS WebSocket client)
Project 4 — E-Commerce Order Processing System
Difficulty: Advanced | Estimated time: 6–8 weeks
What You Build
A backend system for order processing — product catalogue, shopping cart, order placement, payment simulation, inventory management, and order status tracking. Designed as a set of loosely coupled services communicating via an in-process event bus (or Kafka for the full version).
Features
- Product catalogue with search and filtering
- Shopping cart (persisted per session)
- Order placement with stock validation
- Payment processing (simulated with configurable success/failure rate)
- Inventory updates on order confirmation
- Order status machine: PENDING → PROCESSING → SHIPPED → DELIVERED
- Admin dashboard endpoint with sales reports
- Email notifications (simulated via logs or real SMTP)
Tech Stack
Spring Boot 3
├── Spring Data JPA + PostgreSQL
├── Spring Events (or Apache Kafka for advanced version)
├── Spring Scheduler (order status updates)
├── Flyway (schema migrations)
├── Spring Boot Actuator (health, metrics)
├── Micrometer + Prometheus + Grafana (observability)
├── JUnit 5 + Mockito + TestContainers (testing)
└── Docker Compose (full local stack)
Sample Order State Machine
@Service
public class OrderService {
public Order place(long customerId, List<CartItem> items) {
validateInventory(items);
Order order = orderRepo.save(Order.pending(customerId, items));
eventBus.publish(new OrderPlacedEvent(order.id()));
return order;
}
@EventListener
public void onPaymentSuccess(PaymentSucceededEvent event) {
orderRepo.findById(event.orderId())
.ifPresent(order -> {
order.transitionTo(OrderStatus.PROCESSING);
orderRepo.save(order);
eventBus.publish(new OrderConfirmedEvent(order.id()));
});
}
@EventListener
public void onPaymentFailure(PaymentFailedEvent event) {
orderRepo.findById(event.orderId())
.ifPresent(order -> {
order.transitionTo(OrderStatus.CANCELLED);
orderRepo.save(order);
inventoryService.releaseReservation(order);
});
}
}
Skills You Practice
- Domain-driven design with a rich domain model
- Event-driven architecture with Spring Events
- State machine pattern for order lifecycle
- Transactional boundaries and optimistic locking
- Observability with Micrometer metrics
- Complex integration testing with TestContainers
Project 5 — Static Site Generator
Difficulty: Advanced | Estimated time: 5–7 weeks
What You Build
A command-line tool that converts a directory of Markdown files with YAML front matter into a complete HTML website — similar in concept to Hugo or Jekyll, but written in Java. Supports templates, a development server with live reload, and a configurable build pipeline.
Features
- Parse Markdown files with YAML front matter
- Apply Handlebars/Freemarker templates
- Generate a complete HTML site in an output directory
- Copy static assets (CSS, images, JS)
- Built-in dev server with live reload on file change
- Tag and category pages auto-generated
- Sitemap.xml and RSS feed generation
- Incremental builds — only regenerate changed files
Tech Stack
Core Java 21
├── CommonMark — Markdown to HTML parsing
├── SnakeYAML — YAML front matter parsing
├── Freemarker — HTML templating
├── WatchService — file system change detection
├── jdk.httpserver — built-in lightweight dev server
├── PicoCLI — CLI argument parsing
└── JUnit 5 — testing
Sample Build Pipeline
public class SiteBuilder {
public void build(Path sourceDir, Path outputDir) throws IOException {
var pages = discoverPages(sourceDir);
// Process in parallel for large sites
pages.parallelStream().forEach(page -> {
try {
var parsed = markdownParser.parse(page);
var rendered = templateEngine.render(parsed, globalContext);
var outPath = resolveOutputPath(page, outputDir);
Files.createDirectories(outPath.getParent());
Files.writeString(outPath, rendered);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
copyStaticAssets(sourceDir.resolve("static"), outputDir);
generateSitemap(pages, outputDir);
generateRssFeed(pages, outputDir);
}
public void serve(Path sourceDir, Path outputDir, int port) throws IOException {
build(sourceDir, outputDir);
// File watcher for live reload
WatchService watcher = FileSystems.getDefault().newWatchService();
sourceDir.register(watcher,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
Thread.ofVirtual().start(() -> {
while (true) {
WatchKey key = watcher.take();
// Rebuild changed pages and notify browser via SSE
rebuildChanged(key.pollEvents(), sourceDir, outputDir);
key.reset();
}
});
startDevServer(outputDir, port);
System.out.println("Serving at http://localhost:" + port);
}
}
Skills You Practice
- File system APIs (NIO.2, WatchService, Path)
- Parallel processing with streams and virtual threads
- Template engines
- Building a real CLI tool with PicoCLI
- Parsing structured file formats (Markdown, YAML)
- Incremental computation and caching
Choosing Your Project
| Your Goal | Recommended Project |
|---|---|
| Learn core Java + OOP | Project 1 — Finance Tracker |
| Learn Spring Boot REST APIs | Project 2 — URL Shortener |
| Learn WebSockets + real-time | Project 3 — Chat App |
| Learn distributed systems | Project 4 — Order System |
| Learn file processing + CLI | Project 5 — Static Site Generator |
| All of the above | Build them in order — each builds on the previous |