Building a Task Tracker with Spring Boot and Thymeleaf

Spring Boot paired with Thymeleaf remains one of the most approachable combinations for Java developers who want to deliver working web applications without the overhead of a full front-end build chain. Whether you are prototyping an internal tool for a small team in Melbourne or sketching a personal project on the weekend, the framework gives you everything you need in a single dependency.

A task tracker is a classic first project because it touches every layer of a typical web application: a domain model, persistence, a service layer, HTTP handlers, and server-rendered views. Building one from scratch helps you understand how the pieces fit together, and the result is something genuinely useful rather than a throwaway demo.

This walkthrough focuses on clarity over cleverness. We will create a single-user tracker with create, list, complete, and delete operations, then look at ways to extend it once the basics work.

Project setup and dependencies

Start by generating a new Spring Boot project through the Spring Initializr or your IDE. For this build you need three starters: spring-boot-starter-web for the MVC infrastructure, spring-boot-starter-thymeleaf for the templating engine, and spring-boot-starter-data-jpa so we can persist tasks with minimal boilerplate. Add the H2 database driver for local development; you can swap it for PostgreSQL later without changing any Java code.

Australian teams working on internal tools often follow the conventions of the Essential Eight maturity model, and a tracker that stores only the minimum personal data needed is easier to align with those practices. Keep the dependency tree lean and document each choice in your README so reviewers understand why each library is included.

Once the project skeleton is in place, configure application.properties for the H2 console and a small initial data set so you can see something running on http://localhost:8080 within minutes.

The Task entity and JPA repository

The domain model for a task tracker is intentionally simple: an id, a title, an optional description, a boolean flag for completion, and a timestamp. Annotate the class with @Entity, mark the id with @Id and @GeneratedValue, and you have a working JPA mapping. Lombok can remove the getter and setter noise if your team uses it; otherwise, write them out explicitly so newcomers can read the model without an extra plugin.

The repository layer is where Spring Data shines. Declare an interface that extends JpaRepository<Task, Long>, and Spring generates the standard CRUD methods at startup. You can add derived queries such as findByCompletedFalseOrderByCreatedAtDesc() to power an "active tasks" view without writing a single line of SQL.

If you plan to deploy the tracker inside an organisation bound by the Privacy Act 1988, treat task descriptions as potentially sensitive and avoid logging them in plain text during debugging.

Service layer and business rules

A thin service class sits between the controller and the repository and is the right place for any business rules. For a basic tracker this might include trimming whitespace from titles, capping descriptions at a sensible length, and ensuring completed tasks cannot be accidentally re-opened to an inconsistent state.

Returning Optional<Task> from service methods forces callers to handle the missing case, which is a habit worth building early. It is far better than returning null and discovering the bug three sprints later during a code review in Sydney or Brisbane.

Keep transaction boundaries at the service layer with @Transactional rather than at the controller, so the rules stay consistent even if a new caller, such as a scheduled job or a REST endpoint, is added later.

Controllers, forms, and validation

The web layer uses a single @Controller class that maps URLs such as /, /tasks, /tasks/new, and /tasks/{id}/delete. Spring MVC model attributes carry the Task object between the form view and the POST handler. Bind the form with th:object and individual fields with th:field so Thymeleaf handles value rendering, error messages, and CSRF tokens automatically.

Server-side validation comes from Jakarta Bean Validation annotations such as @NotBlank on the title and @Size(max = 500) on the description. When validation fails, Spring returns the form view with the BindingResult populated, and Thymeleaf displays the errors next to the relevant input.

For teams already comfortable with the shopping list implementation pattern, the controller code here will feel familiar, since both projects follow the same create-read-update-delete flow.

Thymeleaf templates and layout

Templates live under src/main/resources/templates. A layout.html fragment using th:replace lets every page share the same header, footer, and navigation without copying markup. The list page iterates over tasks with th:each, conditionally renders a "completed" badge, and exposes delete and edit links.

Use semantic HTML and accessible labels so the tracker works for users who rely on screen readers, a consideration that aligns with the Disability Discrimination Act when the tool is used in an Australian workplace. Bootstrap or a small custom stylesheet is enough to make the interface presentable for a demo.

Keep templates free of business logic. If you find yourself writing complex expressions in the markup, move that logic back into the service layer where it can be unit tested.

Habits that keep the project maintainable

Once the tracker runs locally, the real learning happens as you extend it. Adding filters, due dates, user accounts, or a REST API for a mobile companion each teach a new aspect of the framework. Treat the first version as a foundation and add features one at a time so you can measure the impact of each change.

Adopting a few habits from the start pays off the moment the codebase grows beyond a single screen: