diff --git a/readme.md b/readme.md
index 4a5123c097c5d7f5251a770b7520247516e2da89..e1d989d9ab7c10a3ecea3ea729e399f19011ebeb 100644
--- a/readme.md
+++ b/readme.md
@@ -294,6 +294,17 @@ Command line to run:
 mvn compile jib:build -X -DjibSerialize=true -Djib.to.auth.username=xxx -Djib.to.auth.password=xxxxx
 ```
 
+## Performance Testing
+
+To benchmark the scalability of the PetClinic REST API, a JMeter test plan is available.
+
+- See the [JMeter Performance Test](src/test/jmeter/README.md) for details.
+- Run the test using:
+  ```sh
+  jmeter -n -t src/test/jmeter/petclinic-jmeter-crud-benchmark.jmx \
+  -Jthreads=100 -Jduration=600 -Jops=2000 -Jramp_time=120 \
+  -l results/petclinic-test-results.jtl
+  
 ## Interesting Spring Petclinic forks
 
 The Spring Petclinic master branch in the main [spring-projects](https://github.com/spring-projects/spring-petclinic)
diff --git a/src/test/jmeter/README.md b/src/test/jmeter/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..03407375d5ec6824f44a45071c96110c5eb1cbe1
--- /dev/null
+++ b/src/test/jmeter/README.md
@@ -0,0 +1,107 @@
+# PetClinic JMeter Performance Test
+
+## Overview
+
+This JMeter test plan is designed to benchmark and measure the scalability of the **Spring PetClinic REST API**. The test performs CRUD operations on the API endpoints, simulating real-world usage patterns.
+
+### What This Test Covers
+
+- Creating, updating, retrieving, and deleting **owners** and **pets**.
+- Scheduling and managing **vet visits**.
+- Configurable workload parameters for **scalability testing**.
+
+## Running the Test
+
+### Prerequisites
+
+Ensure you have the following installed:
+
+- **Apache JMeter 5.6.3+** ([Download here](https://jmeter.apache.org/download_jmeter.cgi))
+- **Spring PetClinic REST API running locally**
+    ```sh
+    # Navigate to the Spring PetClinic REST project directory and start the application
+    cd /path/to/spring-petclinic-rest
+    mvn spring-boot:run
+    ```
+
+_(Runs on http://localhost:9966 by default)_
+
+### Running the Test from CLI
+
+Run the JMeter test in **non-GUI mode**:
+
+```sh
+jmeter -n -t src/test/jmeter/petclinic-jmeter-crud-benchmark.jmx \
+ -Jthreads=100 -Jduration=600 -Jops=2000 -Jramp_time=120 \
+ -l results/petclinic-test-results.jtl
+```
+
+#### CLI Parameters
+
+| Parameter     | Description                          | Default Value |
+|---------------|--------------------------------------|---------------|
+| `threads`     | Number of concurrent users           | 50            |
+| `duration`    | Duration of the test (seconds)       | 300           |
+| `ops`         | Target throughput (operations/sec)   | 1000          |
+| `ramp_time`   | Time to ramp up threads (seconds)    | 60            |
+
+## Analyzing Test Results
+
+1. **Generate an HTML Report**
+
+    To generate an **interactive HTML performance report**:
+    ```sh
+    jmeter -g results/petclinic-test-results.jtl -o results/html-report
+    ```
+
+    Then open `results/html-report/index.html` in your browser.
+
+2. **View Raw Results in CLI**
+
+    To quickly inspect the last 20 results:
+    ```sh
+    tail -n 20 results/petclinic-test-results.jtl
+    ```
+
+3. **Process CSV Results for Custom Analysis**
+
+    JMeter results are stored as `.jtl` files, which use `CSV` format by default unless configured otherwise. You can analyze them using:
+    - Python Pandas:
+        ```python
+        import pandas as pd
+        df = pd.read_csv('results/petclinic-test-results.jtl')
+        print(df.describe())
+        ```
+    - Command-line tools (`awk`):
+        ```sh
+        awk -F',' '{print $1, $2, $3}' results/petclinic-test-results.jtl | head -20
+        ```
+### Performance Summary Table
+
+
+| **Transaction**                | **Total Requests** | **Avg Response Time (ms)** | **Min Response Time (ms)** | **Max Response Time (ms)** | **90th Percentile (ms)** | **95th Percentile (ms)** | **99th Percentile (ms)** |
+|--------------------------------|--------------------|----------------------------|----------------------------|----------------------------|--------------------------|--------------------------|--------------------------|
+| **Add Pet to Owner**           | 60,284             | 60.17                      | 1                          | 534                        | 151.00                   | 190.00                   | 255.15                   |
+| **Create Owner**               | 60,284             | 145.94                     | 2                          | 696                        | 315.00                   | 368.00                   | 476.15                   |
+| **Delete Owner**               | 60,224             | 158.76                     | 2                          | 825                        | 322.00                   | 375.00                   | 490.00                   |
+| **Delete Pet**                 | 60,239             | 179.94                     | 2                          | 829                        | 358.00                   | 411.00                   | 550.00                   |
+| **Get Pet Belonging to Owner** | 60,249             | 70.37                      | 1                          | 479                        | 171.00                   | 208.00                   | 260.50                   |
+| **Schedule Visit**             | 60,279             | 166.48                     | 2                          | 880                        | 329.00                   | 381.00                   | 507.20                   |
+| **Update Owner**               | 60,264             | 163.76                     | 2                          | 681                        | 345.00                   | 396.00                   | 486.35                   |
+| **Update Pet**                 | 60,249             | 33.53                      | 2                          | 402                        | 84.00                    | 108.50                   | 161.50                   |
+
+
+**Explanation:**
+- **Total Requests**: Number of executions of the request.
+- **Avg Response Time (ms)**: Average time taken for the request.
+- **Min/Max Response Time (ms)**: The shortest and longest recorded response times.
+- **Percentile Metrics**: The 90th, 95th, and 99th percentile response times show performance under load.
+
+
+## Next Steps
+
+- Run with different configurations to simulate varied workloads.
+- Integrate into CI/CD pipelines for automated performance testing.
+___
+
+Contribute: Feel free to submit PRs to improve the test coverage or parameters!
\ No newline at end of file
diff --git a/src/test/jmeter/petclinic-jmeter-crud-benchmark.jmx b/src/test/jmeter/petclinic-jmeter-crud-benchmark.jmx
new file mode 100644
index 0000000000000000000000000000000000000000..0c5dae8dd4bb564f8acdfa18de7f7661296b6185
--- /dev/null
+++ b/src/test/jmeter/petclinic-jmeter-crud-benchmark.jmx
@@ -0,0 +1,235 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
+  <hashTree>
+    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="PetClinic CRUD Benchmark Test">
+      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables">
+        <collectionProp name="Arguments.arguments">
+          <elementProp name="cores" elementType="Argument">
+            <stringProp name="Argument.name">cores</stringProp>
+            <stringProp name="Argument.value">${__P(cores,2)}</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="threads" elementType="Argument">
+            <stringProp name="Argument.name">threads</stringProp>
+            <stringProp name="Argument.value">${__P(threads,50)}</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="ops" elementType="Argument">
+            <stringProp name="Argument.name">ops</stringProp>
+            <stringProp name="Argument.value">${__P(ops,1000)}</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="duration" elementType="Argument">
+            <stringProp name="Argument.name">duration</stringProp>
+            <stringProp name="Argument.value">${__P(duration,300)}</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="ramp_time" elementType="Argument">
+            <stringProp name="Argument.name">ramp_time</stringProp>
+            <stringProp name="Argument.value">${__P(ramp_time,60)}</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+        </collectionProp>
+      </elementProp>
+    </TestPlan>
+    <hashTree>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Scalability Load Test">
+        <stringProp name="ThreadGroup.num_threads">${threads}</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">${ramp_time}</stringProp>
+        <stringProp name="ThreadGroup.duration">${duration}</stringProp>
+        <boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
+        <boolProp name="ThreadGroup.scheduler">true</boolProp>
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller">
+          <intProp name="LoopController.loops">-1</intProp>
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+        </elementProp>
+      </ThreadGroup>
+      <hashTree>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Owner" enabled="true">
+          <stringProp name="HTTPSampler.path">/petclinic/api/owners</stringProp>
+          <stringProp name="HTTPSampler.method">POST</stringProp>
+          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+          <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="HTTPArgument">
+                <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                <stringProp name="Argument.value">{&quot;firstName&quot;: &quot;John&quot;, &quot;lastName&quot;: &quot;Doe&quot;, &quot;address&quot;: &quot;1234 Elm St&quot;, &quot;city&quot;: &quot;Austin&quot;, &quot;telephone&quot;: &quot;5121234567&quot;}</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+        </HTTPSamplerProxy>
+        <hashTree>
+          <JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="Extract Owner ID" enabled="true">
+            <stringProp name="JSONPostProcessor.referenceNames">owner_id</stringProp>
+            <stringProp name="JSONPostProcessor.jsonPathExprs">$.id</stringProp>
+            <stringProp name="JSONPostProcessor.match_numbers">1</stringProp>
+          </JSONPostProcessor>
+          <hashTree/>
+        </hashTree>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Pet to Owner" enabled="true">
+          <stringProp name="HTTPSampler.path">/petclinic/api/owners/${owner_id}/pets</stringProp>
+          <stringProp name="HTTPSampler.method">POST</stringProp>
+          <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="HTTPArgument">
+                <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                <stringProp name="Argument.value">{&quot;name&quot;: &quot;Buddy&quot;, &quot;birthDate&quot;: &quot;2022-06-15&quot;, &quot;type&quot;: {&quot;id&quot;: 1, &quot;name&quot;: &quot;dog&quot;}}</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+        </HTTPSamplerProxy>
+        <hashTree>
+          <JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="Extract Pet ID" enabled="true">
+            <stringProp name="JSONPostProcessor.referenceNames">pet_id</stringProp>
+            <stringProp name="JSONPostProcessor.jsonPathExprs">$.id</stringProp>
+            <stringProp name="JSONPostProcessor.match_numbers">1</stringProp>
+          </JSONPostProcessor>
+          <hashTree/>
+        </hashTree>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Schedule Visit" enabled="true">
+          <stringProp name="HTTPSampler.path">/petclinic/api/visits</stringProp>
+          <stringProp name="HTTPSampler.method">POST</stringProp>
+          <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="HTTPArgument">
+                <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                <stringProp name="Argument.value">{&quot;petId&quot;: &quot;${pet_id}&quot;, &quot;date&quot;: &quot;2025-02-21&quot;, &quot;description&quot;: &quot;Annual check-up&quot;}</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+        </HTTPSamplerProxy>
+        <hashTree/>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Update Owner" enabled="true">
+          <stringProp name="HTTPSampler.path">/petclinic/api/owners/${owner_id}</stringProp>
+          <stringProp name="HTTPSampler.method">PUT</stringProp>
+          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+          <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="HTTPArgument">
+                <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                <stringProp name="Argument.value">{&quot;firstName&quot;: &quot;JohnUpdated&quot;, &quot;lastName&quot;: &quot;DoeUpdated&quot;, &quot;address&quot;: &quot;5678 Oak St&quot;, &quot;city&quot;: &quot;Houston&quot;, &quot;telephone&quot;: &quot;7139876543&quot;}</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+        </HTTPSamplerProxy>
+        <hashTree/>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Update Pet" enabled="true">
+          <stringProp name="HTTPSampler.path">/petclinic/api/pets/${pet_id}</stringProp>
+          <stringProp name="HTTPSampler.method">PUT</stringProp>
+          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+          <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="HTTPArgument">
+                <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                <stringProp name="Argument.value">{&quot;name&quot;: &quot;Buddy Updated&quot;, &quot;birthDate&quot;: &quot;2022-06-20&quot;, &quot;type&quot;: {&quot;id&quot;: 1, &quot;name&quot;: &quot;dog&quot;}}</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+        </HTTPSamplerProxy>
+        <hashTree/>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Pet Belonging to Owner" enabled="true">
+          <stringProp name="HTTPSampler.path">/petclinic/api/owners/${owner_id}/pets/${pet_id}</stringProp>
+          <stringProp name="HTTPSampler.method">GET</stringProp>
+          <boolProp name="HTTPSampler.postBodyRaw">false</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables">
+            <collectionProp name="Arguments.arguments"/>
+          </elementProp>
+        </HTTPSamplerProxy>
+        <hashTree/>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Delete Pet" enabled="true">
+          <stringProp name="HTTPSampler.path">/petclinic/api/pets/${pet_id}</stringProp>
+          <stringProp name="HTTPSampler.method">DELETE</stringProp>
+          <boolProp name="HTTPSampler.postBodyRaw">false</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables">
+            <collectionProp name="Arguments.arguments"/>
+          </elementProp>
+        </HTTPSamplerProxy>
+        <hashTree/>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Delete Owner" enabled="true">
+          <stringProp name="HTTPSampler.path">/petclinic/api/owners/${owner_id}</stringProp>
+          <stringProp name="HTTPSampler.method">DELETE</stringProp>
+          <boolProp name="HTTPSampler.postBodyRaw">false</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables">
+            <collectionProp name="Arguments.arguments"/>
+          </elementProp>
+        </HTTPSamplerProxy>
+        <hashTree/>
+      </hashTree>
+      <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report">
+        <boolProp name="ResultCollector.error_logging">false</boolProp>
+        <objProp>
+          <name>saveConfig</name>
+          <value class="SampleSaveConfiguration">
+            <time>true</time>
+            <latency>true</latency>
+            <timestamp>true</timestamp>
+            <success>true</success>
+            <label>true</label>
+            <code>true</code>
+            <message>true</message>
+            <threadName>true</threadName>
+            <dataType>true</dataType>
+            <encoding>false</encoding>
+            <assertions>true</assertions>
+            <subresults>true</subresults>
+            <responseData>false</responseData>
+            <samplerData>false</samplerData>
+            <xml>false</xml>
+            <fieldNames>true</fieldNames>
+            <responseHeaders>false</responseHeaders>
+            <requestHeaders>false</requestHeaders>
+            <responseDataOnError>false</responseDataOnError>
+            <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+            <assertionsResultsToSave>0</assertionsResultsToSave>
+            <bytes>true</bytes>
+            <sentBytes>true</sentBytes>
+            <url>true</url>
+            <threadCounts>true</threadCounts>
+            <idleTime>true</idleTime>
+            <connectTime>true</connectTime>
+          </value>
+        </objProp>
+        <stringProp name="filename"></stringProp>
+      </ResultCollector>
+      <hashTree/>
+      <ConstantThroughputTimer guiclass="TestBeanGUI" testclass="ConstantThroughputTimer" testname="Throughput Control">
+        <intProp name="calcMode">0</intProp>
+        <stringProp name="throughput">${ops}</stringProp>
+      </ConstantThroughputTimer>
+      <hashTree/>
+      <ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="HTTP Request Defaults" enabled="true">
+        <stringProp name="HTTPSampler.domain">localhost</stringProp>
+        <stringProp name="HTTPSampler.port">9966</stringProp>
+        <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables">
+          <collectionProp name="Arguments.arguments"/>
+        </elementProp>
+        <stringProp name="HTTPSampler.implementation"></stringProp>
+      </ConfigTestElement>
+      <hashTree/>
+      <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true">
+        <collectionProp name="HeaderManager.headers">
+          <elementProp name="Content-Type" elementType="Header">
+            <stringProp name="Header.name">Content-Type</stringProp>
+            <stringProp name="Header.value">application/json</stringProp>
+          </elementProp>
+          <elementProp name="Accept" elementType="Header">
+            <stringProp name="Header.name">Accept</stringProp>
+            <stringProp name="Header.value">application/json</stringProp>
+          </elementProp>
+        </collectionProp>
+      </HeaderManager>
+      <hashTree/>
+    </hashTree>
+  </hashTree>
+</jmeterTestPlan>