DEV Community

Marcus Hellberg
Marcus Hellberg

Posted on

How we built Spring PetClinic 100% in Java

If you've been part of the Spring community for a while, you may have run into the PetClinic sample application. PetClinic aims to give you a realistic example application showcasing everyday development tasks with Spring technologies.

The original Spring and Thymeleaf implementation of PetClinic

The original Spring and Thymeleaf implementation of PetClinic.

In addition to being the official Spring demo app, there is a thriving community of alternate implementations on GitHub. Having the same example implemented with different frontend and backend technologies can be helpful for learning about new technologies and understanding how they differ from each other.

In the past month, we spent some time implementing both Vaadin Flow and Fusion versions of PetClinic. I'll walk through some highlights in the Flow version in this post.

Vaadin Flow version of PetClinic

Vaadin Flow version of PetClinic

Building a web app frontend without HTML

As you look through the code of the Vaadin Flow PetClinic application, one thing will hit you: there are no HTML templates. This is because the views are implemented in Java.

As a concrete example, compare how pet owners are listed with Thymeleaf templates with how they are listed in Flow.

<table id="owners" class="table table-striped">
  <thead>
  <tr>
    <th style="width: 150px;">Name</th>
    <th style="width: 200px;">Address</th>
    <th>City</th>
    <th style="width: 120px">Telephone</th>
    <th>Pets</th>
  </tr>
  </thead>
  <tbody>
  <tr th:each="owner : ${listOwners}">
    <td>
      <a th:href="@{/owners/__${owner.id}__}" th:text="${owner.firstName + ' ' + owner.lastName}"/></a>
    </td>
    <td th:text="${owner.address}"/>
    <td th:text="${owner.city}"/>
    <td th:text="${owner.telephone}"/>
    <td><span th:text="${#strings.listJoin(owner.pets, ', ')}"/></td>
  </tr>
  </tbody>
</table>
<div th:if="${totalPages > 1}">
  <span>Pages:</span>
  <span>[</span>
  <span th:each="i: ${#numbers.sequence(1, totalPages)}">
      <a th:if="${currentPage != i}" th:href="@{'/owners/?page=' + ${i}}">[[${i}]]</a>
      <span th:unless="${currentPage != i}">[[${i}]]</span>
    </span>
  <span>]&nbsp;</span>
  <span>
      <a th:if="${currentPage > 1}" th:href="@{'/owners/?page=1'}" title="First"
         class="fa fa-fast-backward"></a>
      <span th:unless="${currentPage > 1}" title="First" class="fa fa-fast-backward"></span>
    </span>
  <span>
      <a th:if="${currentPage > 1}" th:href="@{'/owners/?page=' + ${currentPage - 1}}" title="Previous"
         class="fa fa-step-backward"></a>
      <span th:unless="${currentPage > 1}" title="Previous" class="fa fa-step-backward"></span>
    </span>
  <span>
      <a th:if="${currentPage < totalPages}" th:href="@{'/owners/?page=' + ${currentPage + 1}}" title="Next"
         class="fa fa-step-forward"></a>
      <span th:unless="${currentPage < totalPages}" title="Next" class="fa fa-step-forward"></span>
    </span>
  <span>
      <a th:if="${currentPage < totalPages}" th:href="@{'/owners/?page=' + ${totalPages}}" title="Last"
         class="fa fa-fast-forward"></a>
      <span th:unless="${currentPage < totalPages}" title="Last" class="fa fa-step-forward"></span>
    </span>
</div>
Enter fullscreen mode Exit fullscreen mode

Listing owners in a paged table with Thymeleaf

In Vaadin Flow, you define and configure the data grid in Java. The Grid component is automatically paged.

ownersGrid = new Grid<>();
ownersGrid.addColumn(new ComponentRenderer<>(owner -> 
    new RouterLink(
                owner.getFirstName() + " " + owner.getLastName(), // Link caption
                OwnerDetailsView.class, // Destination page
                new RouteParameters("ownerId", // Parameters
                        owner.getId().toString()))))
        .setHeader(getTranslation("name"));
ownersGrid.addColumn(Owner::getAddress).setHeader(getTranslation("address"));
ownersGrid.addColumn(Owner::getCity).setHeader(getTranslation("city"));
ownersGrid.addColumn(Owner::getTelephone).setHeader(getTranslation(("telephone")));
ownersGrid.addColumn(owner -> owner.getPets().stream().map(Pet::toString)
        .collect(Collectors.joining(", ")))
        .setHeader(getTranslation("pets"));
Enter fullscreen mode Exit fullscreen mode

The resulting Vaadin app is a single-page web application that runs in all modern browsers and does not require any plugins.

Run Vaadin Flow PetClinic locally

Try out the Vaadin Fusion on your own computer with the following commands (make sure you have Java 8+ and Maven installed):

git clone https://github.com/spring-petclinic/spring-petclinic-vaadin-flow
cd spring-petclinic-vaadin-flow
mvn spring-boot:run
Enter fullscreen mode Exit fullscreen mode

You can now access the app on localhost:8080. The frontend code is in the org.springframework.samples.petclinic.ui package.

Discussion (0)