Best Spring Boot Projects for Beginners: Learn by Doing
Purpose
When I started learning Spring Boot, I made the mistake of jumping straight into complex microservices tutorials. I got overwhelmed by configuration files, dependency injection, and annotations I didn’t understand. The problem wasn’t Spring Boot—it was that I hadn’t built enough foundational projects first.
Over the past few years, I’ve worked with dozens of junior developers who made the same mistake. They wanted to build distributed systems before understanding how a simple REST controller works. That’s why I recommend starting small and progressively adding complexity.
In this post, I’ll share the 7 best Spring Boot projects for beginners that I’ve used to teach developers effectively. Each project builds on the previous one, introducing new concepts gradually. You’ll go from a simple “Hello World” to building applications with scheduling, caching, and complex database relationships.
These projects assume you know basic Java (classes, interfaces, collections) and understand what a REST API is. You’ll need Java 17+ and an IDE like IntelliJ IDEA.
Project 1: Hello World REST API (Difficulty: ★☆☆☆☆)
What You’ll Learn
I recommend starting here because it teaches you the Spring Boot fundamentals without the distraction of databases or complex logic. You’ll learn how Spring Boot’s auto-configuration works, how to create a REST endpoint, and how to run the application.
Tech Stack
- Spring Boot 3.x
- Spring Web
- Java 17+
Building the Project
Create a new Spring Boot project using Spring Initializr (start.spring.io) with the “Spring Web” dependency. Here’s the simplest REST controller you can write:
@RestControllerpublic class HelloController {
@GetMapping("/hello") public Map<String, String> hello() { return Map.of("message", "Hello, Spring Boot!"); }}That’s it. When you run the application, Spring Boot starts an embedded Tomcat server on port 8080. Visit http://localhost:8080/hello and you’ll see:
{"message":"Hello, Spring Boot!"}Why This Works
I like this starting point because it teaches three critical concepts:
- @RestController: Tells Spring to treat this class as a REST endpoint handler
- @GetMapping: Maps HTTP GET requests to specific methods
- Auto-configuration: Spring Boot automatically configures Tomcat, Spring MVC, and other components
Time to complete: 30 minutes
Project 2: Todo Management System (Difficulty: ★★☆☆☆)
What You’ll Learn
This is where I introduce databases and CRUD operations. You’ll build a complete Todo API with endpoints to create, read, update, and delete items. This project teaches the layered architecture pattern that you’ll use throughout your Spring Boot career.
Tech Stack
- Spring Boot 3.x
- Spring Data JPA
- H2 Database (in-memory)
- Spring Web
Building the Todo Entity
Start with the JPA entity that represents a todo:
@Entitypublic class Todo { @Id @GeneratedValue private Long id;
@Column(nullable = false) private String title;
private String description;
private Boolean completed = false;
private LocalDate dueDate;
// Constructors, getters, setters}Creating the Repository
Spring Data JPA gives you CRUD operations for free:
public interface TodoRepository extends JpaRepository<Todo, Long> { List<Todo> findByCompleted(Boolean completed); List<Todo> findByDueDateBefore(LocalDate date);}Building the Service Layer
I always recommend separating business logic from the controller:
@Servicepublic class TodoService {
private final TodoRepository repository;
public TodoService(TodoRepository repository) { this.repository = repository; }
public List<Todo> findAll() { return repository.findAll(); }
public Todo findById(Long id) { return repository.findById(id) .orElseThrow(() -> new RuntimeException("Todo not found")); }
public Todo create(Todo todo) { return repository.save(todo); }
public Todo update(Long id, Todo updated) { Todo todo = findById(id); todo.setTitle(updated.getTitle()); todo.setDescription(updated.getDescription()); todo.setCompleted(updated.getCompleted()); todo.setDueDate(updated.getDueDate()); return repository.save(todo); }
public void delete(Long id) { repository.deleteById(id); }}REST Controller
Finally, expose the endpoints:
@RestController@RequestMapping("/api/todos")public class TodoController {
private final TodoService service;
public TodoController(TodoService service) { this.service = service; }
@GetMapping public List<Todo> getAllTodos() { return service.findAll(); }
@GetMapping("/{id}") public Todo getTodo(@PathVariable Long id) { return service.findById(id); }
@PostMapping public Todo createTodo(@RequestBody Todo todo) { return service.create(todo); }
@PutMapping("/{id}") public Todo updateTodo(@PathVariable Long id, @RequestBody Todo todo) { return service.update(id, todo); }
@DeleteMapping("/{id}") public void deleteTodo(@PathVariable Long id) { service.delete(id); }}Why This Works
This project teaches the core pattern I use in every Spring Boot application: Controller → Service → Repository. You’ll learn how JPA entities map to database tables, how Spring Data JPA generates SQL automatically, and how to structure your code for maintainability.
Time to complete: 2-3 hours
Project 3: Simple Blog Engine (Difficulty: ★★☆☆☆)
What You’ll Learn
I use this project to teach entity relationships and pagination. You’ll build a blog where posts have multiple comments, learning about one-to-many relationships and how to handle them efficiently.
Tech Stack
- Spring Boot 3.x
- Spring Data JPA
- H2 Database
- Spring Web
- Validation (Jakarta)
Building the Entities
@Entitypublic class Post { @Id @GeneratedValue private Long id;
@Column(nullable = false) private String title;
@Column(columnDefinition = "TEXT") private String content;
private LocalDateTime createdAt;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "post") private List<Comment> comments = new ArrayList<>();
// Constructors, getters, setters}
@Entitypublic class Comment { @Id @GeneratedValue private Long id;
@Column(nullable = false) private String author;
@Column(columnDefinition = "TEXT") private String text;
@ManyToOne @JoinColumn(name = "post_id") private Post post;
// Constructors, getters, setters}Adding Pagination
Pagination is critical for real applications. Here’s how I implement it:
@GetMapping("/posts")public Page<Post> getAllPosts( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(defaultValue = "createdAt") String sortBy) { return postService.findAll(PageRequest.of(page, size, Sort.by(sortBy)));}Global Exception Handling
I teach exception handling at this stage because it’s essential for good APIs:
@RestControllerAdvicepublic class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class) public ResponseEntity<Map<String, String>> handleNotFound(RuntimeException ex) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(Map.of("error", ex.getMessage())); }}Why This Works
This project introduces relationships between entities, which is fundamental to database-driven applications. You’ll learn about cascade operations, how to avoid the N+1 query problem, and how to implement pagination for large datasets.
Time to complete: 4-5 hours
Project 4: Weather App (Difficulty: ★★★☆☆)
What You’ll Learn
External API integration is something you’ll do constantly in real projects. I use a weather app because it’s practical and demonstrates caching, configuration management, and error handling for external services.
Tech Stack
- Spring Boot 3.x
- Spring Web
- RestTemplate
- Spring Cache (Caffeine)
- OpenWeatherMap API (free tier)
Building the Weather Service
@Servicepublic class WeatherService {
@Value("${weather.api.key}") private String apiKey;
private final RestTemplate restTemplate;
public WeatherService(RestTemplate restTemplate) { this.restTemplate = restTemplate; }
@Cacheable(value = "weather", key = "#city") public WeatherData getWeather(String city) { String url = String.format( "https://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s&units=metric", city, apiKey ); return restTemplate.getForObject(url, WeatherData.class); }}Configuration
Use application.properties for externalized configuration:
weather.api.key=your_api_key_hereweather.api.base-url=https://api.openweathermap.org/data/2.5weather.api.timeout=5000
spring.cache.type=caffeinespring.cache.cache-names=weatherspring.cache.caffeine.spec=maximumSize=100,expireAfterWrite=10mEnable Caching
@SpringBootApplication@EnableCachingpublic class WeatherApplication { public static void main(String[] args) { SpringApplication.run(WeatherApplication.class, args); }
@Bean public RestTemplate restTemplate() { return new RestTemplate(); }}Why This Works
This project teaches you how to integrate with third-party APIs, which is a crucial skill. The caching aspect shows you how to optimize performance by reducing external API calls. You’ll also learn proper configuration management using Spring’s @Value annotation.
Time to complete: 3-4 hours
Project 5: Employee Management System (Difficulty: ★★★☆☆)
What You’ll Learn
I introduce many-to-many relationships here because they’re common in business applications. You’ll build an HR system managing employees and departments, learning about complex queries and transaction management.
Tech Stack
- Spring Boot 3.x
- Spring Data JPA
- PostgreSQL or MySQL
- Spring Web
Building the Entities
@Entitypublic class Employee { @Id @GeneratedValue private Long id;
private String firstName; private String lastName; private String email; private BigDecimal salary; private LocalDate hireDate;
@ManyToMany @JoinTable( name = "employee_department", joinColumns = @JoinColumn(name = "employee_id"), inverseJoinColumns = @JoinColumn(name = "department_id") ) private Set<Department> departments = new HashSet<>();
// Constructors, getters, setters}
@Entitypublic class Department { @Id @GeneratedValue private Long id;
private String name; private String location;
@ManyToMany(mappedBy = "departments") private Set<Employee> employees = new HashSet<>();
// Constructors, getters, setters}Custom Queries
@Repositorypublic interface EmployeeRepository extends JpaRepository<Employee, Long> {
@Query("SELECT e FROM Employee e JOIN e.departments d WHERE d.name = :deptName") List<Employee> findByDepartmentName(@Param("deptName") String deptName);
@Query("SELECT e FROM Employee e WHERE e.salary > :minSalary") List<Employee> findBySalaryGreaterThan(@Param("minSalary") BigDecimal salary);
Page<Employee> findByLastNameContaining(String lastName, Pageable pageable);}Transaction Management
@Servicepublic class EmployeeService {
@Transactional public void transferToDepartment(Long employeeId, Long departmentId) { Employee employee = employeeRepository.findById(employeeId).orElseThrow(); Department department = departmentRepository.findById(departmentId).orElseThrow();
employee.getDepartments().add(department); employeeRepository.save(employee); }}Why This Works
Many-to-many relationships appear constantly in real applications (users/roles, products/categories, students/courses). This project teaches you how to model these relationships, write custom JPQL queries, and use transactions correctly.
Time to complete: 5-6 hours
Project 6: Book Catalog with Search (Difficulty: ★★★☆☆)
What You’ll Learn
Search functionality is a requirement in most applications I build. I teach this project to show you how to build flexible, dynamic queries using JPA Specifications.
Tech Stack
- Spring Boot 3.x
- Spring Data JPA
- H2 Database
- Spring Web
Building the Book Entity
@Entitypublic class Book { @Id @GeneratedValue private Long id;
private String title; private String author; private String isbn;
@Enumerated(EnumType.STRING) private Genre genre;
private LocalDate publicationDate; private BigDecimal price;
@Column(columnDefinition = "TEXT") private String description;
// Constructors, getters, setters}Dynamic Search with Specifications
public class BookSpecification {
public static Specification<Book> withFilters( String title, String author, Genre genre, BigDecimal minPrice, BigDecimal maxPrice ) { return (root, query, cb) -> { List<Predicate> predicates = new ArrayList<>();
if (title != null) { predicates.add(cb.like( cb.lower(root.get("title")), "%" + title.toLowerCase() + "%" )); }
if (author != null) { predicates.add(cb.like( cb.lower(root.get("author")), "%" + author.toLowerCase() + "%" )); }
if (genre != null) { predicates.add(cb.equal(root.get("genre"), genre)); }
if (minPrice != null) { predicates.add(cb.greaterThanOrEqualTo(root.get("price"), minPrice)); }
if (maxPrice != null) { predicates.add(cb.lessThanOrEqualTo(root.get("price"), maxPrice)); }
return cb.and(predicates.toArray(new Predicate[0])); }; }}Search Endpoint
@GetMapping("/books/search")public Page<Book> searchBooks( @RequestParam(required = false) String title, @RequestParam(required = false) String author, @RequestParam(required = false) Genre genre, @RequestParam(required = false) BigDecimal minPrice, @RequestParam(required = false) BigDecimal maxPrice, Pageable pageable) { Specification<Book> spec = BookSpecification.withFilters( title, author, genre, minPrice, maxPrice ); return bookRepository.findAll(spec, pageable);}Why This Works
Static query methods (like findByTitle) aren’t enough for real-world search requirements. This project teaches you the Specification pattern, which lets you build dynamic queries based on whatever filters the user provides. It’s a pattern I use in production constantly.
Time to complete: 4-5 hours
Project 7: Task Scheduling Application (Difficulty: ★★★★☆)
What You’ll Learn
The final project introduces background processing and email notifications. You’ll build a task reminder system that checks for upcoming tasks and sends emails automatically.
Tech Stack
- Spring Boot 3.x
- Spring Web
- Mail API
- Spring Scheduling
- H2 Database
Scheduled Tasks
@Componentpublic class TaskScheduler {
private final TaskRepository taskRepository; private final EmailService emailService;
public TaskScheduler(TaskRepository taskRepository, EmailService emailService) { this.taskRepository = taskRepository; this.emailService = emailService; }
@Scheduled(cron = "0 0 * * * ?") public void checkUpcomingTasks() { List<Task> upcomingTasks = taskRepository.findDueWithinHours(24);
upcomingTasks.forEach(task -> emailService.sendReminder(task) ); }
@Scheduled(cron = "0 0 2 * * ?") public void cleanupCompletedTasks() { LocalDateTime cutoff = LocalDateTime.now().minusDays(30); taskRepository.deleteCompletedBefore(cutoff); }}Async Email Service
@Servicepublic class EmailService {
private final JavaMailSender mailSender;
public EmailService(JavaMailSender mailSender) { this.mailSender = mailSender; }
@Async public void sendReminder(Task task) { SimpleMailMessage message = new SimpleMailMessage(); message.setTo(task.getUserEmail()); message.setSubject("Task Reminder: " + task.getTitle()); message.setText("Your task is due soon!");
mailSender.send(message); }}Enable Scheduling and Async
@SpringBootApplication@EnableScheduling@EnableAsyncpublic class TaskSchedulingApplication { public static void main(String[] args) { SpringApplication.run(TaskSchedulingApplication.class, args); }}Why This Works
Background jobs are essential for real applications (cleanup tasks, data synchronization, notifications). This project teaches you cron expressions, asynchronous processing, and email integration—all skills you’ll use in production systems.
Time to complete: 4-5 hours
Best Practices
Layered Architecture
Always maintain separation of concerns in your Spring Boot projects. I’ve seen too many codebases where developers put business logic directly in controllers or database queries in services. Keep it clean:
- Controller: Handle HTTP requests/responses only
- Service: Business logic and validation
- Repository: Data access only
DTOs Over Entities in APIs
Never expose JPA entities directly to your API. This is a mistake I see beginners make constantly. Instead, use DTOs (Data Transfer Objects):
// GOOD - Using DTOspublic record UserResponse(String name, String email) {}
@PatchMapping("/users/{id}")public UserResponse updateUser(@PathVariable Long id, @RequestBody UpdateUserRequest request) { User user = userService.update(id, request); return new UserResponse(user.getName(), user.getEmail());}
// BAD - Exposing internal entity@PatchMapping("/users/{id}")public User updateUser(@PathVariable Long id, @RequestBody User user) { return userService.update(id, user);}Exception Handling
Use @ControllerAdvice for global exception handling. It keeps your controllers clean and provides consistent error responses:
@RestControllerAdvicepublic class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new ErrorResponse(ex.getMessage())); }}Configuration Management
Never hardcode configuration values. Use application.properties or application.yml:
# Database configurationspring.datasource.url=jdbc:postgresql://localhost:5432/mydbspring.datasource.username=${DB_USER}spring.datasource.password=${DB_PASSWORD}
# External API configurationweather.api.key=${WEATHER_API_KEY}weather.api.timeout=5000Common Mistakes to Avoid
N+1 Query Problem: Always use @EntityGraph or JOIN FETCH when loading relationships to avoid executing separate queries for each related entity.
Overusing @Transactional: Only mark methods that actually need transactions. Read-only operations don’t require transactions.
Ignoring Validation: Always validate input using Jakarta validation annotations (@NotBlank, @Size, @Pattern, etc.) to catch invalid data early.
Summary
The 7 Spring Boot projects for beginners I’ve covered here provide a structured path from basic concepts to real-world skills. Each project builds on the previous one, introducing new concepts gradually without overwhelming you.
Starting with a simple Hello World REST API and progressing through CRUD operations, entity relationships, external API integration, complex queries, dynamic search, and finally background processing—you’ll build a complete skillset.
The key to learning Spring Boot is practice over theory. Don’t just read through these projects—code along, break things, fix them, and experiment. That’s how I learned, and that’s how you’ll truly understand the framework.
By the time you complete all 7 projects, you’ll have built 7 complete applications, mastered REST API development, understood database integration with JPA, and learned industry best practices.
Pick a project, open your IDE, and start building.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments