diff --git a/README.md b/README.md
index 7d77e94..2af0c8e 100644
--- a/README.md
+++ b/README.md
@@ -70,14 +70,22 @@ quarkus.datasource.password=password
quarkus.datasource.jdbc.min-size=1
quarkus.datasource.jdbc.max-size=1
quarkus.devservices.enabled=false
+quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQLDialect
+quarkus.hibernate-orm.unsupported-properties."hibernate.boot.allow_jdbc_metadata_access"=false
```
-### Spring Boot - NOT TESTED
+### Spring Boot
```properties
# application-test.properties
spring.datasource.url=jdbc:pglite:memory://
spring.datasource.driver-class-name=io.roastedroot.pglite4j.jdbc.PgLiteDriver
+spring.datasource.username=postgres
+spring.datasource.password=password
+spring.datasource.hikari.maximum-pool-size=1
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
+spring.jpa.properties.hibernate.boot.allow_jdbc_metadata_access=false
```
### HikariCP - NOT TESTED
diff --git a/it/src/it/spring-boot-pet-clinic/invoker.properties b/it/src/it/spring-boot-pet-clinic/invoker.properties
new file mode 100644
index 0000000..84099bc
--- /dev/null
+++ b/it/src/it/spring-boot-pet-clinic/invoker.properties
@@ -0,0 +1 @@
+invoker.goals=test
diff --git a/it/src/it/spring-boot-pet-clinic/pom.xml b/it/src/it/spring-boot-pet-clinic/pom.xml
new file mode 100644
index 0000000..fb4e2ea
--- /dev/null
+++ b/it/src/it/spring-boot-pet-clinic/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.4.4
+
+
+
+ org.acme
+ spring-boot-pet-clinic-it
+ 0.0-SNAPSHOT
+ jar
+
+
+ 17
+ UTF-8
+
+
+
+
+ io.roastedroot
+ pglite4j-jdbc
+ @project.version@
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/it/src/it/spring-boot-pet-clinic/src/main/java/org/acme/Application.java b/it/src/it/spring-boot-pet-clinic/src/main/java/org/acme/Application.java
new file mode 100644
index 0000000..01e439b
--- /dev/null
+++ b/it/src/it/spring-boot-pet-clinic/src/main/java/org/acme/Application.java
@@ -0,0 +1,12 @@
+package org.acme;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/it/src/it/spring-boot-pet-clinic/src/main/java/org/acme/Pet.java b/it/src/it/spring-boot-pet-clinic/src/main/java/org/acme/Pet.java
new file mode 100644
index 0000000..2067c60
--- /dev/null
+++ b/it/src/it/spring-boot-pet-clinic/src/main/java/org/acme/Pet.java
@@ -0,0 +1,50 @@
+package org.acme;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+
+@Entity
+public class Pet {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String name;
+ private String species;
+ private int age;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getSpecies() {
+ return species;
+ }
+
+ public void setSpecies(String species) {
+ this.species = species;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+}
diff --git a/it/src/it/spring-boot-pet-clinic/src/main/java/org/acme/PetController.java b/it/src/it/spring-boot-pet-clinic/src/main/java/org/acme/PetController.java
new file mode 100644
index 0000000..2aa4b77
--- /dev/null
+++ b/it/src/it/spring-boot-pet-clinic/src/main/java/org/acme/PetController.java
@@ -0,0 +1,64 @@
+package org.acme;
+
+import java.util.List;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
+
+@RestController
+@RequestMapping("/pets")
+public class PetController {
+
+ private final PetRepository repository;
+
+ public PetController(PetRepository repository) {
+ this.repository = repository;
+ }
+
+ @GetMapping
+ public List list() {
+ return repository.findAllByOrderByIdAsc();
+ }
+
+ @GetMapping("/{id}")
+ public Pet get(@PathVariable Long id) {
+ return repository
+ .findById(id)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+ }
+
+ @PostMapping
+ @ResponseStatus(HttpStatus.CREATED)
+ public Pet create(@RequestBody Pet pet) {
+ return repository.save(pet);
+ }
+
+ @PutMapping("/{id}")
+ public Pet update(@PathVariable Long id, @RequestBody Pet pet) {
+ Pet existing =
+ repository
+ .findById(id)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+ existing.setName(pet.getName());
+ existing.setSpecies(pet.getSpecies());
+ existing.setAge(pet.getAge());
+ return repository.save(existing);
+ }
+
+ @DeleteMapping("/{id}")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void delete(@PathVariable Long id) {
+ if (!repository.existsById(id)) {
+ throw new ResponseStatusException(HttpStatus.NOT_FOUND);
+ }
+ repository.deleteById(id);
+ }
+}
diff --git a/it/src/it/spring-boot-pet-clinic/src/main/java/org/acme/PetRepository.java b/it/src/it/spring-boot-pet-clinic/src/main/java/org/acme/PetRepository.java
new file mode 100644
index 0000000..d137f5e
--- /dev/null
+++ b/it/src/it/spring-boot-pet-clinic/src/main/java/org/acme/PetRepository.java
@@ -0,0 +1,9 @@
+package org.acme;
+
+import java.util.List;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface PetRepository extends JpaRepository {
+
+ List findAllByOrderByIdAsc();
+}
diff --git a/it/src/it/spring-boot-pet-clinic/src/main/resources/application.properties b/it/src/it/spring-boot-pet-clinic/src/main/resources/application.properties
new file mode 100644
index 0000000..3c903d9
--- /dev/null
+++ b/it/src/it/spring-boot-pet-clinic/src/main/resources/application.properties
@@ -0,0 +1,8 @@
+spring.datasource.url=jdbc:pglite:memory://
+spring.datasource.driver-class-name=io.roastedroot.pglite4j.jdbc.PgLiteDriver
+spring.datasource.username=postgres
+spring.datasource.password=password
+spring.datasource.hikari.maximum-pool-size=1
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
+spring.jpa.properties.hibernate.boot.allow_jdbc_metadata_access=false
diff --git a/it/src/it/spring-boot-pet-clinic/src/test/java/org/acme/PetControllerTest.java b/it/src/it/spring-boot-pet-clinic/src/test/java/org/acme/PetControllerTest.java
new file mode 100644
index 0000000..ed64875
--- /dev/null
+++ b/it/src/it/spring-boot-pet-clinic/src/test/java/org/acme/PetControllerTest.java
@@ -0,0 +1,147 @@
+package org.acme;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.is;
+
+import io.restassured.RestAssured;
+import io.restassured.http.ContentType;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.server.LocalServerPort;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public class PetControllerTest {
+
+ @LocalServerPort private int port;
+
+ private int buddyId;
+ private int whiskersId;
+ private int goldieId;
+
+ @BeforeEach
+ void setUp() {
+ RestAssured.port = port;
+ }
+
+ @Test
+ @Order(1)
+ void createBuddy() {
+ buddyId =
+ given().contentType(ContentType.JSON)
+ .body("{\"name\":\"Buddy\",\"species\":\"Dog\",\"age\":3}")
+ .when()
+ .post("/pets")
+ .then()
+ .statusCode(201)
+ .body("name", is("Buddy"))
+ .body("species", is("Dog"))
+ .body("age", is(3))
+ .extract()
+ .path("id");
+ }
+
+ @Test
+ @Order(2)
+ void createWhiskers() {
+ whiskersId =
+ given().contentType(ContentType.JSON)
+ .body("{\"name\":\"Whiskers\",\"species\":\"Cat\",\"age\":5}")
+ .when()
+ .post("/pets")
+ .then()
+ .statusCode(201)
+ .body("name", is("Whiskers"))
+ .extract()
+ .path("id");
+ }
+
+ @Test
+ @Order(3)
+ void createGoldie() {
+ goldieId =
+ given().contentType(ContentType.JSON)
+ .body("{\"name\":\"Goldie\",\"species\":\"Fish\",\"age\":1}")
+ .when()
+ .post("/pets")
+ .then()
+ .statusCode(201)
+ .body("name", is("Goldie"))
+ .extract()
+ .path("id");
+ }
+
+ @Test
+ @Order(4)
+ void listAll() {
+ given().when()
+ .get("/pets")
+ .then()
+ .statusCode(200)
+ .body("size()", is(3))
+ .body("[0].name", is("Buddy"))
+ .body("[1].name", is("Whiskers"))
+ .body("[2].name", is("Goldie"));
+ }
+
+ @Test
+ @Order(5)
+ void getById() {
+ given().when()
+ .get("/pets/" + buddyId)
+ .then()
+ .statusCode(200)
+ .body("name", is("Buddy"))
+ .body("species", is("Dog"))
+ .body("age", is(3));
+ }
+
+ @Test
+ @Order(6)
+ void updateBuddy() {
+ given().contentType(ContentType.JSON)
+ .body("{\"name\":\"Buddy\",\"species\":\"Dog\",\"age\":4}")
+ .when()
+ .put("/pets/" + buddyId)
+ .then()
+ .statusCode(200)
+ .body("name", is("Buddy"))
+ .body("age", is(4));
+ }
+
+ @Test
+ @Order(7)
+ void verifyUpdate() {
+ given().when().get("/pets/" + buddyId).then().statusCode(200).body("age", is(4));
+ }
+
+ @Test
+ @Order(8)
+ void deleteGoldie() {
+ given().when().delete("/pets/" + goldieId).then().statusCode(204);
+ }
+
+ @Test
+ @Order(9)
+ void listAfterDelete() {
+ given().when()
+ .get("/pets")
+ .then()
+ .statusCode(200)
+ .body("size()", is(2))
+ .body("[0].name", is("Buddy"))
+ .body("[1].name", is("Whiskers"));
+ }
+
+ @Test
+ @Order(10)
+ void getDeletedReturns404() {
+ given().when().get("/pets/" + goldieId).then().statusCode(404);
+ }
+}