This is an automated email from the ASF dual-hosted git repository.

dkuzmenko pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hive.git


The following commit(s) were added to refs/heads/master by this push:
     new fddce4e7cc8 HIVE-29468: Standalone HMS REST Catalog Server with Spring 
Boot (#6327)
fddce4e7cc8 is described below

commit fddce4e7cc8e3d527aa2232778beae123d68d344
Author: Dmitriy Fingerman <[email protected]>
AuthorDate: Fri Mar 13 08:53:28 2026 -0400

    HIVE-29468: Standalone HMS REST Catalog Server with Spring Boot (#6327)
---
 itests/hive-iceberg/pom.xml                        |   6 +
 itests/qtest-iceberg/pom.xml                       |  21 ++
 .../cli/BaseStandaloneRESTCatalogServerTest.java   | 358 +++++++++++++++++++++
 .../hive/cli/TestStandaloneRESTCatalogServer.java  | 225 ++-----------
 .../TestStandaloneRESTCatalogServerJwtAuth.java    | 160 +++++++++
 packaging/pom.xml                                  |   3 +
 pom.xml                                            |   2 +
 .../metastore-rest-catalog/pom.xml                 |  51 +++
 .../standalone/IcebergCatalogConfiguration.java    |  91 ++++++
 .../rest/standalone/RestCatalogServerRuntime.java  | 100 ++++++
 .../standalone/StandaloneRESTCatalogServer.java    | 181 ++---------
 .../health/HMSReadinessHealthIndicator.java        |  69 ++++
 .../src/main/resources/application.yml             |  69 ++++
 .../src/main/resources/keystore.p12                | Bin 0 -> 2714 bytes
 .../metastore/auth/jwt/SimpleJWTAuthenticator.java |   5 +-
 standalone-metastore/pom.xml                       |  82 +++++
 16 files changed, 1066 insertions(+), 357 deletions(-)

diff --git a/itests/hive-iceberg/pom.xml b/itests/hive-iceberg/pom.xml
index 5e661cc65e9..fca0b1e4d8f 100644
--- a/itests/hive-iceberg/pom.xml
+++ b/itests/hive-iceberg/pom.xml
@@ -51,6 +51,12 @@
       <version>${keycloak.version}</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>jakarta.annotation</groupId>
+      <artifactId>jakarta.annotation-api</artifactId>
+      <version>${jakarta.annotation.version}</version>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.apache.hive</groupId>
       <artifactId>hive-standalone-metastore-common</artifactId>
diff --git a/itests/qtest-iceberg/pom.xml b/itests/qtest-iceberg/pom.xml
index bf812192318..3dc736007f4 100644
--- a/itests/qtest-iceberg/pom.xml
+++ b/itests/qtest-iceberg/pom.xml
@@ -475,6 +475,27 @@
       <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
+    <!-- Spring Boot test dependencies -->
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-test</artifactId>
+      <version>${spring-boot.version}</version>
+      <scope>test</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>org.springframework.boot</groupId>
+          <artifactId>spring-boot-starter-logging</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.junit.jupiter</groupId>
+          <artifactId>junit-jupiter</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.junit.vintage</groupId>
+          <artifactId>junit-vintage-engine</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
     <dependency>
       <groupId>org.testcontainers</groupId>
       <artifactId>testcontainers</artifactId>
diff --git 
a/itests/qtest-iceberg/src/test/java/org/apache/hadoop/hive/cli/BaseStandaloneRESTCatalogServerTest.java
 
b/itests/qtest-iceberg/src/test/java/org/apache/hadoop/hive/cli/BaseStandaloneRESTCatalogServerTest.java
new file mode 100644
index 00000000000..1223e18ce90
--- /dev/null
+++ 
b/itests/qtest-iceberg/src/test/java/org/apache/hadoop/hive/cli/BaseStandaloneRESTCatalogServerTest.java
@@ -0,0 +1,358 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hive.cli;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.UUID;
+
+import javax.net.ssl.SSLContext;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.common.FileUtils;
+import org.apache.http.HttpHeaders;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.apache.hadoop.hive.metastore.MetaStoreTestUtils;
+import org.apache.hadoop.hive.metastore.security.HadoopThriftAuthBridge;
+import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
+import org.apache.hadoop.hive.metastore.conf.MetastoreConf.ConfVars;
+import javax.servlet.http.HttpServlet;
+
+import org.apache.iceberg.rest.standalone.IcebergCatalogConfiguration;
+import org.apache.iceberg.rest.standalone.RestCatalogServerRuntime;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.test.context.TestContext;
+import org.springframework.test.context.TestExecutionListener;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Base class for Standalone REST Catalog Server integration tests.
+ *
+ * Provides shared setup (HMS, listeners), HTTP helpers (with optional auth), 
and common tests
+ * (liveness, readiness, Prometheus, server port). Subclasses provide 
auth-specific configuration
+ * and tests.
+ */
+public abstract class BaseStandaloneRESTCatalogServerTest {
+  protected static final Logger LOG = 
LoggerFactory.getLogger(BaseStandaloneRESTCatalogServerTest.class);
+
+  protected static Configuration hmsConf;
+  protected static int hmsPort;
+  protected static File warehouseDir;
+  protected static File hmsTempDir;
+
+  @LocalServerPort
+  private int port;
+
+  @Autowired
+  private RestCatalogServerRuntime server;
+
+  /**
+   * Starts HMS before the Spring ApplicationContext loads.
+   * Spring loads the context before @BeforeClass, so we use a 
TestExecutionListener
+   * which runs before context initialization.
+   */
+  @Order(Ordered.HIGHEST_PRECEDENCE)
+  public static class HmsStartupListener implements TestExecutionListener {
+    private static final String TEMP_DIR_PREFIX = 
"StandaloneRESTCatalogServer";
+
+    @Override
+    public void beforeTestClass(TestContext testContext) throws Exception {
+      if (hmsPort > 0) {
+        return;
+      }
+      String uniqueTestKey = String.format("%s_%s", TEMP_DIR_PREFIX, 
UUID.randomUUID());
+      hmsTempDir = new 
File(MetaStoreTestUtils.getTestWarehouseDir(uniqueTestKey));
+      hmsTempDir.mkdirs();
+      warehouseDir = new File(hmsTempDir, "warehouse");
+      warehouseDir.mkdirs();
+
+      hmsConf = MetastoreConf.newMetastoreConf();
+      MetaStoreTestUtils.setConfForStandloneMode(hmsConf);
+
+      String jdbcUrl = String.format("jdbc:derby:memory:%s;create=true",
+          new File(hmsTempDir, "metastore_db").getAbsolutePath());
+      MetastoreConf.setVar(hmsConf, ConfVars.CONNECT_URL_KEY, jdbcUrl);
+      MetastoreConf.setVar(hmsConf, ConfVars.WAREHOUSE, 
warehouseDir.getAbsolutePath());
+      MetastoreConf.setVar(hmsConf, ConfVars.WAREHOUSE_EXTERNAL, 
warehouseDir.getAbsolutePath());
+
+      hmsPort = MetaStoreTestUtils.startMetaStoreWithRetry(
+          HadoopThriftAuthBridge.getBridge(), hmsConf, true, false, false, 
false);
+      LOG.info("Started embedded HMS on port: {} (before Spring context)", 
hmsPort);
+    }
+  }
+
+  @SpringBootApplication
+  @Import(TestCatalogConfig.class)
+  @ComponentScan(
+      basePackages = "org.apache.iceberg.rest.standalone",
+      excludeFilters = {
+          @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = 
IcebergCatalogConfiguration.class),
+          @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = 
RestCatalogServerRuntime.class)
+      })
+  public static class TestRestCatalogApplication {}
+
+  /**
+   * Test-specific config providing the REST catalog servlet.
+   * Uses Configuration from test's TestConfig (with hmsPort, warehouseDir).
+   * Does NOT import IcebergCatalogConfiguration to avoid production 
hadoopConfiguration.
+   */
+  @org.springframework.context.annotation.Configuration
+  static class TestCatalogConfig {
+
+    @Bean
+    public Configuration hadoopConfiguration() {
+      Configuration conf = createBaseTestConfiguration();
+      MetastoreConf.setVar(conf, ConfVars.CATALOG_SERVLET_AUTH, "none");
+      return conf;
+    }
+
+    @Bean
+    public RestCatalogServerRuntime restCatalogServerRuntime(ServerProperties 
serverProperties) {
+      Configuration conf = createBaseTestConfiguration();
+      MetastoreConf.setVar(conf, ConfVars.CATALOG_SERVLET_AUTH, "none");
+      return new RestCatalogServerRuntime(conf, serverProperties);
+    }
+
+    @Bean
+    public ServletRegistrationBean<HttpServlet> 
restCatalogServlet(Configuration conf) {
+      return IcebergCatalogConfiguration.createRestCatalogServlet(conf);
+    }
+  }
+
+  protected String url(String path) {
+    return UriComponentsBuilder.newInstance()
+        .scheme("https")
+        .host("localhost")
+        .port(getPort())
+        .path(path.startsWith("/") ? path : "/" + path)
+        .toUriString();
+  }
+
+  /**
+   * Creates an HttpClient that trusts the test server's self-signed 
certificate.
+   */
+  protected CloseableHttpClient createHttpClient() throws Exception {
+    SSLContext sslContext = SSLContextBuilder.create()
+        .loadTrustMaterial((chain, authType) -> true)
+        .build();
+    return HttpClients.custom()
+        .setSSLContext(sslContext)
+        .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
+        .build();
+  }
+
+  protected int getPort() {
+    return port;
+  }
+
+  protected RestCatalogServerRuntime getServer() {
+    return server;
+  }
+
+  /**
+   * Creates base test Configuration with HMS URI and warehouse dirs.
+   * Subclasses add auth-specific settings.
+   */
+  protected static Configuration createBaseTestConfiguration() {
+    Configuration conf = MetastoreConf.newMetastoreConf();
+    MetastoreConf.setVar(conf, ConfVars.THRIFT_URIS, "thrift://localhost:" + 
hmsPort);
+    MetastoreConf.setVar(conf, ConfVars.WAREHOUSE, 
warehouseDir.getAbsolutePath());
+    MetastoreConf.setVar(conf, ConfVars.WAREHOUSE_EXTERNAL, 
warehouseDir.getAbsolutePath());
+    MetastoreConf.setVar(conf, ConfVars.ICEBERG_CATALOG_SERVLET_PATH, 
"iceberg");
+    MetastoreConf.setLongVar(conf, ConfVars.CATALOG_SERVLET_PORT, 0);
+    return conf;
+  }
+
+  /**
+   * Returns the Bearer token for catalog API tests, or null if no auth.
+   * Subclasses with auth (e.g. JWT) override to return a valid token.
+   */
+  protected String getBearerTokenForCatalogTests() {
+    return null;
+  }
+
+  /**
+   * Creates a GET request with optional Bearer token.
+   *
+   * @param path the request path (e.g. "/iceberg/v1/config")
+   * @param bearerToken optional Bearer token; if null, no Authorization 
header is set
+   */
+  protected HttpGet get(String path, String bearerToken) {
+    HttpGet request = new HttpGet(url(path));
+    request.setHeader("Content-Type", "application/json");
+    if (bearerToken != null) {
+      request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + bearerToken);
+    }
+    return request;
+  }
+
+  /**
+   * Creates a GET request without auth.
+   */
+  protected HttpGet get(String path) {
+    return get(path, null);
+  }
+
+  /**
+   * Creates a POST request with optional Bearer token.
+   *
+   * @param path the request path
+   * @param jsonBody the JSON body
+   * @param bearerToken optional Bearer token; if null, no Authorization 
header is set
+   */
+  protected HttpPost post(String path, String jsonBody, String bearerToken) {
+    HttpPost request = new HttpPost(url(path));
+    request.setHeader("Content-Type", "application/json");
+    if (bearerToken != null) {
+      request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + bearerToken);
+    }
+    if (jsonBody != null) {
+      request.setEntity(new StringEntity(jsonBody, "UTF-8"));
+    }
+    return request;
+  }
+
+  /**
+   * Creates a POST request without auth.
+   */
+  protected HttpPost post(String path, String jsonBody) {
+    return post(path, jsonBody, null);
+  }
+
+  protected static void teardownBase() throws IOException {
+    if (hmsPort > 0) {
+      MetaStoreTestUtils.close(hmsPort);
+    }
+    if (hmsTempDir != null && hmsTempDir.exists()) {
+      FileUtils.deleteDirectory(hmsTempDir);
+    }
+  }
+
+  @Test(timeout = 60000)
+  public void testLivenessProbe() throws Exception {
+    try (CloseableHttpClient httpClient = createHttpClient();
+        CloseableHttpResponse response = 
httpClient.execute(get("/actuator/health/liveness"))) {
+      assertEquals("Liveness probe should return 200", 200, 
response.getStatusLine().getStatusCode());
+      String body = EntityUtils.toString(response.getEntity());
+      assertTrue("Liveness should be UP", body.contains("UP"));
+      LOG.info("Liveness probe passed: {}", body);
+    }
+  }
+
+  @Test(timeout = 60000)
+  public void testReadinessProbe() throws Exception {
+    try (CloseableHttpClient httpClient = createHttpClient();
+        CloseableHttpResponse response = 
httpClient.execute(get("/actuator/health/readiness"))) {
+      assertEquals("Readiness probe should return 200", 200, 
response.getStatusLine().getStatusCode());
+      String body = EntityUtils.toString(response.getEntity());
+      assertTrue("Readiness should be UP", body.contains("UP"));
+      LOG.info("Readiness probe passed: {}", body);
+    }
+  }
+
+  @Test(timeout = 60000)
+  public void testPrometheusMetrics() throws Exception {
+    try (CloseableHttpClient httpClient = createHttpClient();
+        CloseableHttpResponse response = 
httpClient.execute(get("/actuator/prometheus"))) {
+      assertEquals("Metrics endpoint should return 200", 200, 
response.getStatusLine().getStatusCode());
+      String body = EntityUtils.toString(response.getEntity());
+      assertTrue("Should contain JVM metrics", body.contains("jvm_memory"));
+      LOG.info("Prometheus metrics available");
+    }
+  }
+
+  @Test(timeout = 60000)
+  public void testServerPort() {
+    RestCatalogServerRuntime s = getServer();
+    assertTrue("Server port should be > 0", getPort() > 0);
+    assertNotNull("REST endpoint should not be null", s.getRestEndpoint());
+    LOG.info("Server port: {}, Endpoint: {}", getPort(), s.getRestEndpoint());
+  }
+
+  @Test(timeout = 120000)
+  public void testRESTCatalogConfig() throws Exception {
+    String token = getBearerTokenForCatalogTests();
+    try (CloseableHttpClient httpClient = createHttpClient();
+        CloseableHttpResponse response = 
httpClient.execute(get(String.format("/%s/%s",
+            IcebergCatalogConfiguration.DEFAULT_SERVLET_PATH, "v1/config"), 
token))) {
+      assertEquals("Config endpoint should return 200", 200, 
response.getStatusLine().getStatusCode());
+      String responseBody = EntityUtils.toString(response.getEntity());
+      assertTrue("Response should contain endpoints", 
responseBody.contains("endpoints"));
+      assertTrue("Response should be valid JSON", responseBody.startsWith("{") 
&& responseBody.endsWith("}"));
+    }
+  }
+
+  @Test(timeout = 120000)
+  public void testRESTCatalogNamespaceOperations() throws Exception {
+    String token = getBearerTokenForCatalogTests();
+    String namespacePath = String.format("/%s/%s", 
IcebergCatalogConfiguration.DEFAULT_SERVLET_PATH, "v1/namespaces");
+    String namespaceName = "testdb";
+
+    try (CloseableHttpClient httpClient = createHttpClient()) {
+      try (CloseableHttpResponse response = 
httpClient.execute(get(namespacePath, token))) {
+        assertEquals("List namespaces should return 200", 200, 
response.getStatusLine().getStatusCode());
+      }
+
+      try (CloseableHttpResponse response = httpClient.execute(
+          post(namespacePath, "{\"namespace\":[\"" + namespaceName + "\"]}", 
token))) {
+        assertEquals("Create namespace should return 200", 200, 
response.getStatusLine().getStatusCode());
+      }
+
+      try (CloseableHttpResponse response = 
httpClient.execute(get(namespacePath, token))) {
+        assertEquals("List namespaces after creation should return 200",
+            200, response.getStatusLine().getStatusCode());
+        String responseBody = EntityUtils.toString(response.getEntity());
+        assertTrue("Response should contain created namespace", 
responseBody.contains(namespaceName));
+      }
+
+      try (CloseableHttpResponse response = httpClient.execute(
+          get(String.format("/%s/%s/%s", 
IcebergCatalogConfiguration.DEFAULT_SERVLET_PATH,
+              "v1/namespaces", namespaceName), token))) {
+        assertEquals("Get namespace should return 200",
+            200, response.getStatusLine().getStatusCode());
+        String responseBody = EntityUtils.toString(response.getEntity());
+        assertTrue("Response should contain namespace", 
responseBody.contains(namespaceName));
+      }
+    }
+  }
+}
diff --git 
a/itests/qtest-iceberg/src/test/java/org/apache/hadoop/hive/cli/TestStandaloneRESTCatalogServer.java
 
b/itests/qtest-iceberg/src/test/java/org/apache/hadoop/hive/cli/TestStandaloneRESTCatalogServer.java
index a5ec398d4b2..8998dbf77e3 100644
--- 
a/itests/qtest-iceberg/src/test/java/org/apache/hadoop/hive/cli/TestStandaloneRESTCatalogServer.java
+++ 
b/itests/qtest-iceberg/src/test/java/org/apache/hadoop/hive/cli/TestStandaloneRESTCatalogServer.java
@@ -17,211 +17,40 @@
  */
 package org.apache.hadoop.hive.cli;
 
-import java.io.File;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
-import org.apache.http.util.EntityUtils;
-import org.apache.hadoop.hive.metastore.MetaStoreTestUtils;
-import org.apache.hadoop.hive.metastore.security.HadoopThriftAuthBridge;
-import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
-import org.apache.hadoop.hive.metastore.conf.MetastoreConf.ConfVars;
-import org.apache.iceberg.rest.standalone.StandaloneRESTCatalogServer;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import java.io.IOException;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import org.junit.AfterClass;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.junit4.SpringRunner;
 
 /**
- * Integration test for Standalone REST Catalog Server.
- * 
+ * Integration test for Standalone REST Catalog Server with Spring Boot (no 
auth).
+ *
  * Tests that the standalone server can:
- * 1. Start independently of HMS
+ * 1. Start independently of HMS using Spring Boot
  * 2. Connect to an external HMS instance
  * 3. Serve REST Catalog requests
- * 4. Provide health check endpoint
+ * 4. Provide health check endpoints (liveness and readiness)
+ * 5. Expose Prometheus metrics
  */
-public class TestStandaloneRESTCatalogServer {
-  private static final Logger LOG = 
LoggerFactory.getLogger(TestStandaloneRESTCatalogServer.class);
-  
-  private Configuration hmsConf;
-  private Configuration restCatalogConf;
-  private int hmsPort;
-  private StandaloneRESTCatalogServer restCatalogServer;
-  private File warehouseDir;
-  private File hmsTempDir;
-  
-  @Before
-  public void setup() throws Exception {
-    // Setup temporary directories
-    hmsTempDir = new File(System.getProperty("java.io.tmpdir"), "test-hms-" + 
System.currentTimeMillis());
-    hmsTempDir.mkdirs();
-    warehouseDir = new File(hmsTempDir, "warehouse");
-    warehouseDir.mkdirs();
-    
-    // Configure and start embedded HMS
-    hmsConf = MetastoreConf.newMetastoreConf();
-    MetaStoreTestUtils.setConfForStandloneMode(hmsConf);
-    
-    String jdbcUrl = String.format("jdbc:derby:memory:%s;create=true",
-        new File(hmsTempDir, "metastore_db").getAbsolutePath());
-    MetastoreConf.setVar(hmsConf, ConfVars.CONNECT_URL_KEY, jdbcUrl);
-    MetastoreConf.setVar(hmsConf, ConfVars.WAREHOUSE, 
warehouseDir.getAbsolutePath());
-    MetastoreConf.setVar(hmsConf, ConfVars.WAREHOUSE_EXTERNAL, 
warehouseDir.getAbsolutePath());
-    
-    // Start HMS
-    hmsPort = MetaStoreTestUtils.startMetaStoreWithRetry(
-        HadoopThriftAuthBridge.getBridge(), hmsConf, true, false, false, 
false);
-    LOG.info("Started embedded HMS on port: {}", hmsPort);
-    
-    // Configure standalone REST Catalog server
-    restCatalogConf = MetastoreConf.newMetastoreConf();
-    String hmsUri = "thrift://localhost:" + hmsPort;
-    MetastoreConf.setVar(restCatalogConf, ConfVars.THRIFT_URIS, hmsUri);
-    MetastoreConf.setVar(restCatalogConf, ConfVars.WAREHOUSE, 
warehouseDir.getAbsolutePath());
-    MetastoreConf.setVar(restCatalogConf, ConfVars.WAREHOUSE_EXTERNAL, 
warehouseDir.getAbsolutePath());
-    
-    // Configure REST Catalog servlet
-    int restPort = MetaStoreTestUtils.findFreePort();
-    MetastoreConf.setLongVar(restCatalogConf, ConfVars.CATALOG_SERVLET_PORT, 
restPort);
-    MetastoreConf.setVar(restCatalogConf, 
ConfVars.ICEBERG_CATALOG_SERVLET_PATH, "iceberg");
-    MetastoreConf.setVar(restCatalogConf, ConfVars.CATALOG_SERVLET_AUTH, 
"none");
-    
-    // Start standalone REST Catalog server
-    restCatalogServer = new StandaloneRESTCatalogServer(restCatalogConf);
-    restCatalogServer.start();
-    LOG.info("Started standalone REST Catalog server on port: {}", 
restCatalogServer.getPort());
-  }
-  
-  @After
-  public void teardown() {
-    if (restCatalogServer != null) {
-      restCatalogServer.stop();
-    }
-    if (hmsPort > 0) {
-      MetaStoreTestUtils.close(hmsPort);
-    }
-    if (hmsTempDir != null && hmsTempDir.exists()) {
-      deleteDirectory(hmsTempDir);
-    }
-  }
-  
-  @Test(timeout = 60000)
-  public void testHealthCheck() throws Exception {
-    LOG.info("=== Test: Health Check ===");
-    
-    String healthUrl = "http://localhost:"; + restCatalogServer.getPort() + 
"/health";
-    try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
-      HttpGet request = new HttpGet(healthUrl);
-      try (CloseableHttpResponse response = httpClient.execute(request)) {
-        assertEquals("Health check should return 200", 200, 
response.getStatusLine().getStatusCode());
-        LOG.info("Health check passed");
-      }
-    }
-  }
-  
-  @Test(timeout = 60000)
-  public void testRESTCatalogConfig() throws Exception {
-    LOG.info("=== Test: REST Catalog Config Endpoint ===");
-    
-    String configUrl = restCatalogServer.getRestEndpoint() + "/v1/config";
-    try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
-      HttpGet request = new HttpGet(configUrl);
-      try (CloseableHttpResponse response = httpClient.execute(request)) {
-        assertEquals("Config endpoint should return 200", 200, 
response.getStatusLine().getStatusCode());
-        
-        String responseBody = EntityUtils.toString(response.getEntity());
-        LOG.info("Config response: {}", responseBody);
-        // ConfigResponse should contain endpoints, defaults, and overrides
-        assertTrue("Response should contain endpoints", 
responseBody.contains("endpoints"));
-        assertTrue("Response should be valid JSON", 
responseBody.startsWith("{") && responseBody.endsWith("}"));
-      }
-    }
-  }
-  
-  @Test(timeout = 60000)
-  public void testRESTCatalogNamespaceOperations() throws Exception {
-    LOG.info("=== Test: REST Catalog Namespace Operations ===");
-    
-    String namespacesUrl = restCatalogServer.getRestEndpoint() + 
"/v1/namespaces";
-    String namespaceName = "testdb";
-    
-    try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
-      // List namespaces (before creation)
-      HttpGet listRequest = new HttpGet(namespacesUrl);
-      listRequest.setHeader("Content-Type", "application/json");
-      try (CloseableHttpResponse response = httpClient.execute(listRequest)) {
-        assertEquals("List namespaces should return 200", 200, 
response.getStatusLine().getStatusCode());
-      }
-      
-      // Create namespace - REST Catalog API requires JSON body with namespace 
array
-      HttpPost createRequest = new HttpPost(namespacesUrl);
-      createRequest.setHeader("Content-Type", "application/json");
-      String jsonBody = "{\"namespace\":[\"" + namespaceName + "\"]}";
-      createRequest.setEntity(new StringEntity(jsonBody, "UTF-8"));
-      
-      try (CloseableHttpResponse response = httpClient.execute(createRequest)) 
{
-        assertEquals("Create namespace should return 200", 200, 
response.getStatusLine().getStatusCode());
-      }
-      
-      // Verify namespace exists by checking it in the list
-      HttpGet listAfterRequest = new HttpGet(namespacesUrl);
-      listAfterRequest.setHeader("Content-Type", "application/json");
-      try (CloseableHttpResponse response = 
httpClient.execute(listAfterRequest)) {
-        assertEquals("List namespaces after creation should return 200", 
-            200, response.getStatusLine().getStatusCode());
-        
-        String responseBody = EntityUtils.toString(response.getEntity());
-        LOG.info("Namespaces list response: {}", responseBody);
-        assertTrue("Response should contain created namespace", 
responseBody.contains(namespaceName));
-      }
-      
-      // Verify namespace exists by getting it directly
-      String getNamespaceUrl = restCatalogServer.getRestEndpoint() + 
"/v1/namespaces/" + namespaceName;
-      HttpGet getRequest = new HttpGet(getNamespaceUrl);
-      getRequest.setHeader("Content-Type", "application/json");
-      try (CloseableHttpResponse response = httpClient.execute(getRequest)) {
-        assertEquals("Get namespace should return 200", 
-            200, response.getStatusLine().getStatusCode());
-        String responseBody = EntityUtils.toString(response.getEntity());
-        LOG.info("Get namespace response: {}", responseBody);
-        assertTrue("Response should contain namespace", 
responseBody.contains(namespaceName));
-      }
-    }
-    
-    LOG.info("Namespace operations passed");
-  }
-  
-  @Test(timeout = 60000)
-  public void testServerPort() {
-    LOG.info("=== Test: Server Port ===");
-    assertTrue("Server port should be > 0", restCatalogServer.getPort() > 0);
-    assertNotNull("REST endpoint should not be null", 
restCatalogServer.getRestEndpoint());
-    LOG.info("Server port: {}, Endpoint: {}", restCatalogServer.getPort(), 
restCatalogServer.getRestEndpoint());
-  }
-  
-  private void deleteDirectory(File directory) {
-    if (directory.exists()) {
-      File[] files = directory.listFiles();
-      if (files != null) {
-        for (File file : files) {
-          if (file.isDirectory()) {
-            deleteDirectory(file);
-          } else {
-            file.delete();
-          }
-        }
-      }
-      directory.delete();
+@RunWith(SpringRunner.class)
+@SpringBootTest(
+    classes = 
BaseStandaloneRESTCatalogServerTest.TestRestCatalogApplication.class,
+    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+    properties = {
+        
"spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration",
+        "spring.main.allow-bean-definition-overriding=true"
     }
+)
+@TestExecutionListeners(
+    listeners = BaseStandaloneRESTCatalogServerTest.HmsStartupListener.class,
+    mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
+)
+public class TestStandaloneRESTCatalogServer extends 
BaseStandaloneRESTCatalogServerTest {
+  @AfterClass
+  public static void teardownClass() throws IOException {
+    teardownBase();
   }
 }
diff --git 
a/itests/qtest-iceberg/src/test/java/org/apache/hadoop/hive/cli/TestStandaloneRESTCatalogServerJwtAuth.java
 
b/itests/qtest-iceberg/src/test/java/org/apache/hadoop/hive/cli/TestStandaloneRESTCatalogServerJwtAuth.java
new file mode 100644
index 00000000000..c0a168b44a6
--- /dev/null
+++ 
b/itests/qtest-iceberg/src/test/java/org/apache/hadoop/hive/cli/TestStandaloneRESTCatalogServerJwtAuth.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hive.cli;
+
+import java.io.IOException;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
+import org.apache.hadoop.hive.metastore.conf.MetastoreConf.ConfVars;
+import org.apache.iceberg.rest.extension.OAuth2AuthorizationServer;
+import org.apache.iceberg.rest.standalone.RestCatalogServerRuntime;
+import org.junit.AfterClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.Ordered;
+import org.springframework.test.context.TestContext;
+import org.springframework.test.context.TestExecutionListener;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Integration test for Standalone REST Catalog Server with JWT authentication.
+ *
+ * Uses Keycloak (via Testcontainers) as the real OIDC server - matching the 
design of
+ * existing OAuth2 tests (TestRESTCatalogOAuth2Jwt). Verifies that the 
standalone server correctly
+ * enforces JWT auth.
+ *
+ * <p>Requires Docker to be available (Testcontainers starts Keycloak in a 
container).
+ *
+ * <p>Verifies:
+ * - Accepts valid JWT tokens from Keycloak
+ * - Rejects invalid/malformed tokens
+ * - Rejects requests without a Bearer token
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(
+    classes = {
+        BaseStandaloneRESTCatalogServerTest.TestRestCatalogApplication.class,
+        TestStandaloneRESTCatalogServerJwtAuth.TestConfig.class
+    },
+    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+    properties = {
+        
"spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration",
+        "spring.main.allow-bean-definition-overriding=true"
+    }
+)
+@TestExecutionListeners(
+    listeners = {
+        BaseStandaloneRESTCatalogServerTest.HmsStartupListener.class,
+        TestStandaloneRESTCatalogServerJwtAuth.KeycloakStartupListener.class
+    },
+    mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
+)
+public class TestStandaloneRESTCatalogServerJwtAuth extends 
BaseStandaloneRESTCatalogServerTest {
+  private static OAuth2AuthorizationServer authorizationServer;
+
+  @Override
+  protected String getBearerTokenForCatalogTests() {
+    return authorizationServer != null ? authorizationServer.getAccessToken() 
: null;
+  }
+
+  @Order(Ordered.HIGHEST_PRECEDENCE - 1)
+  public static class KeycloakStartupListener implements TestExecutionListener 
{
+    @Override
+    public void beforeTestClass(TestContext testContext) throws Exception {
+      if (authorizationServer != null) {
+        return;
+      }
+      // Use accessTokenHeaderTypeRfc9068=false so Keycloak emits "JWT" (not 
"at+jwt") in the token
+      // header - SimpleJWTAuthenticator accepts null and JWT but not "at+jwt" 
by default.
+      authorizationServer = new OAuth2AuthorizationServer(
+          org.testcontainers.containers.Network.newNetwork(), false);
+      authorizationServer.start();
+      LOG.info("Started Keycloak authorization server at {}", 
authorizationServer.getIssuer());
+    }
+  }
+
+  @TestConfiguration
+  static class TestConfig {
+    @Bean
+    @Primary
+    public Configuration hadoopConfiguration() {
+      Configuration conf = createBaseTestConfiguration();
+      MetastoreConf.setVar(conf, ConfVars.CATALOG_SERVLET_AUTH, "jwt");
+      MetastoreConf.setVar(conf, 
ConfVars.THRIFT_METASTORE_AUTHENTICATION_JWT_JWKS_URL,
+          authorizationServer.getIssuer() + "/protocol/openid-connect/certs");
+      return conf;
+    }
+
+    @Bean
+    @Primary
+    public RestCatalogServerRuntime restCatalogServerRuntime(ServerProperties 
serverProperties) {
+      Configuration conf = createBaseTestConfiguration();
+      MetastoreConf.setVar(conf, ConfVars.CATALOG_SERVLET_AUTH, "jwt");
+      MetastoreConf.setVar(conf, 
ConfVars.THRIFT_METASTORE_AUTHENTICATION_JWT_JWKS_URL,
+          authorizationServer.getIssuer() + "/protocol/openid-connect/certs");
+      return new RestCatalogServerRuntime(conf, serverProperties);
+    }
+  }
+
+  @AfterClass
+  public static void teardownClass() throws IOException {
+    if (authorizationServer != null) {
+      try {
+        authorizationServer.stop();
+      } catch (Exception e) {
+        LOG.warn("Failed to stop Keycloak (may not have started): {}", 
e.getMessage());
+      }
+    }
+    teardownBase();
+  }
+
+  @Test(timeout = 60000)
+  public void testRESTCatalogRejectsInvalidToken() throws Exception {
+    LOG.info("=== Test: REST Catalog Rejects Invalid JWT ===");
+
+    String invalidToken = "invalid-token-not-a-valid-jwt";
+    try (CloseableHttpClient httpClient = createHttpClient();
+        CloseableHttpResponse response = 
httpClient.execute(get("/iceberg/v1/config", invalidToken))) {
+      assertEquals("Config endpoint with invalid JWT should return 401", 401, 
response.getStatusLine().getStatusCode());
+      LOG.info("Invalid JWT correctly rejected");
+    }
+  }
+
+  @Test(timeout = 60000)
+  public void testRESTCatalogRejectsRequestWithoutToken() throws Exception {
+    LOG.info("=== Test: REST Catalog Rejects Request Without Token ===");
+
+    try (CloseableHttpClient httpClient = createHttpClient();
+        CloseableHttpResponse response = 
httpClient.execute(get("/iceberg/v1/config"))) {
+      assertEquals("Config endpoint without token should return 401", 401, 
response.getStatusLine().getStatusCode());
+      LOG.info("Request without token correctly rejected");
+    }
+  }
+}
diff --git a/packaging/pom.xml b/packaging/pom.xml
index 9b9ff3c8b49..46949bd66b7 100644
--- a/packaging/pom.xml
+++ b/packaging/pom.xml
@@ -184,6 +184,9 @@
                 <MIT>
                   https?://(www\.)?opensource\.org/licenses/mit(-license.php)?
                 </MIT>
+                <public-domain-1.0.html>
+                  https?://creativecommons\.org/publicdomain/zero/1\.0/?
+                </public-domain-1.0.html>
               </licenseUrlFileNames>
               <licenseUrlFileNameSanitizers>
                 <licenseUrlFileNameSanitizer>
diff --git a/pom.xml b/pom.xml
index 084b007a4b2..e2c5ee8ab46 100644
--- a/pom.xml
+++ b/pom.xml
@@ -228,6 +228,7 @@
     <rs-api.version>2.0.1</rs-api.version>
     <json-path.version>2.9.0</json-path.version>
     <janino.version>3.1.12</janino.version>
+    <jakarta.annotation.version>2.1.1</jakarta.annotation.version>
     <datasketches.version>2.0.0</datasketches.version>
     <spotbugs.version>4.8.6</spotbugs.version>
     <validation-api.version>1.1.0.Final</validation-api.version>
@@ -235,6 +236,7 @@
     <jansi.version>2.4.0</jansi.version>
     <!-- If upgrading, upgrade atlas as well in ql/pom.xml, which brings in 
some springframework dependencies transitively -->
     <spring.version>5.3.39</spring.version>
+    <spring-boot.version>2.7.18</spring-boot.version>
     <spring.ldap.version>2.4.4</spring.ldap.version>
     
<project.build.outputTimestamp>2025-01-01T00:00:00Z</project.build.outputTimestamp>
     <keycloak.version>26.0.6</keycloak.version>
diff --git a/standalone-metastore/metastore-rest-catalog/pom.xml 
b/standalone-metastore/metastore-rest-catalog/pom.xml
index fde65eddc34..d987f7cce97 100644
--- a/standalone-metastore/metastore-rest-catalog/pom.xml
+++ b/standalone-metastore/metastore-rest-catalog/pom.xml
@@ -42,6 +42,34 @@
       <artifactId>hive-iceberg-catalog</artifactId>
       <version>${hive.version}</version>
     </dependency>
+    <!-- Spring Boot dependencies - use Jetty instead of Tomcat for Java 21 
compatibility -->
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-web</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>org.springframework.boot</groupId>
+          <artifactId>spring-boot-starter-logging</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.springframework.boot</groupId>
+          <artifactId>spring-boot-starter-tomcat</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-jetty</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-actuator</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.micrometer</groupId>
+      <artifactId>micrometer-registry-prometheus</artifactId>
+      <version>1.9.17</version>
+    </dependency>
     <!-- Test dependencies -->
     <dependency>
       <groupId>org.apache.hive</groupId>
@@ -236,6 +264,13 @@
       <artifactId>keycloak-admin-client</artifactId>
       <scope>test</scope>
     </dependency>
+    <!-- Required by keycloak-admin-client/Resteasy for 
jakarta.annotation.Priority -->
+    <dependency>
+      <groupId>jakarta.annotation</groupId>
+      <artifactId>jakarta.annotation-api</artifactId>
+      <version>${jakarta.annotation.version}</version>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.testcontainers</groupId>
       <artifactId>testcontainers</artifactId>
@@ -303,6 +338,22 @@
           </execution>
         </executions>
       </plugin>
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+        <version>${spring-boot.version}</version>
+        <configuration>
+          
<mainClass>org.apache.iceberg.rest.standalone.StandaloneRESTCatalogServer</mainClass>
+          <classifier>exec</classifier>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>repackage</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
     </plugins>
   </build>
 </project>
diff --git 
a/standalone-metastore/metastore-rest-catalog/src/main/java/org/apache/iceberg/rest/standalone/IcebergCatalogConfiguration.java
 
b/standalone-metastore/metastore-rest-catalog/src/main/java/org/apache/iceberg/rest/standalone/IcebergCatalogConfiguration.java
new file mode 100644
index 00000000000..7f30b3221a9
--- /dev/null
+++ 
b/standalone-metastore/metastore-rest-catalog/src/main/java/org/apache/iceberg/rest/standalone/IcebergCatalogConfiguration.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.iceberg.rest.standalone;
+
+import javax.servlet.http.HttpServlet;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.metastore.ServletServerBuilder;
+import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
+import org.apache.hadoop.hive.metastore.conf.MetastoreConf.ConfVars;
+import org.apache.iceberg.rest.HMSCatalogFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Spring configuration for the Iceberg REST Catalog.
+ */
[email protected]
+public class IcebergCatalogConfiguration {
+  private static final Logger LOG = 
LoggerFactory.getLogger(IcebergCatalogConfiguration.class);
+  public static final String DEFAULT_SERVLET_PATH = "iceberg";
+  public static final int DEFAULT_PORT = 8080;
+
+  @Bean
+  public Configuration hadoopConfiguration(ApplicationArguments args) {
+    Configuration conf = MetastoreConf.newMetastoreConf();
+    for (String arg : args.getSourceArgs()) {
+      if (arg.startsWith("-D")) {
+        String[] kv = arg.substring(2).split("=", 2);
+        if (kv.length == 2) {
+          conf.set(kv[0], kv[1]);
+        }
+      }
+    }
+    return conf;
+  }
+
+  @Bean
+  public ServletRegistrationBean<HttpServlet> restCatalogServlet(Configuration 
conf) {
+    return createRestCatalogServlet(conf);
+  }
+
+  /**
+   * Creates the REST Catalog servlet registration. Shared by production 
config and tests.
+   */
+  public static ServletRegistrationBean<HttpServlet> 
createRestCatalogServlet(Configuration conf) {
+    String servletPath = MetastoreConf.getVar(conf, 
ConfVars.ICEBERG_CATALOG_SERVLET_PATH);
+    if (servletPath == null || servletPath.isEmpty()) {
+      servletPath = DEFAULT_SERVLET_PATH;
+      MetastoreConf.setVar(conf, ConfVars.ICEBERG_CATALOG_SERVLET_PATH, 
servletPath);
+    }
+
+    int port = MetastoreConf.getIntVar(conf, ConfVars.CATALOG_SERVLET_PORT);
+    if (port == 0) {
+      port = DEFAULT_PORT;
+      MetastoreConf.setLongVar(conf, ConfVars.CATALOG_SERVLET_PORT, port);
+    }
+
+    LOG.info("Creating REST Catalog servlet at /{}", servletPath);
+
+    ServletServerBuilder.Descriptor descriptor = 
HMSCatalogFactory.createServlet(conf);
+    if (descriptor == null || descriptor.getServlet() == null) {
+      throw new IllegalStateException("Failed to create Iceberg REST Catalog 
servlet");
+    }
+
+    ServletRegistrationBean<HttpServlet> registration =
+        new ServletRegistrationBean<>(descriptor.getServlet(), "/" + 
servletPath + "/*");
+    registration.setName("IcebergRESTCatalog");
+    registration.setLoadOnStartup(1);
+
+    return registration;
+  }
+}
diff --git 
a/standalone-metastore/metastore-rest-catalog/src/main/java/org/apache/iceberg/rest/standalone/RestCatalogServerRuntime.java
 
b/standalone-metastore/metastore-rest-catalog/src/main/java/org/apache/iceberg/rest/standalone/RestCatalogServerRuntime.java
new file mode 100644
index 00000000000..403c4a4423d
--- /dev/null
+++ 
b/standalone-metastore/metastore-rest-catalog/src/main/java/org/apache/iceberg/rest/standalone/RestCatalogServerRuntime.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.iceberg.rest.standalone;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
+import org.apache.hadoop.hive.metastore.conf.MetastoreConf.ConfVars;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.web.context.WebServerInitializedEvent;
+import org.springframework.boot.web.server.Ssl;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+import org.springframework.web.util.UriComponentsBuilder;
+
+/**
+ * Runtime lifecycle for the Standalone REST Catalog Server.
+ * Holds port, rest endpoint, and handles web server initialization.
+ */
+@Component
+public class RestCatalogServerRuntime {
+  private static final Logger LOG = 
LoggerFactory.getLogger(RestCatalogServerRuntime.class);
+
+  private final Configuration conf;
+  private final ServerProperties serverProperties;
+  private String restEndpoint;
+  private int port;
+
+  public RestCatalogServerRuntime(Configuration conf, ServerProperties 
serverProperties) {
+    this.conf = conf;
+    this.serverProperties = serverProperties;
+
+    String thriftUris = MetastoreConf.getVar(conf, ConfVars.THRIFT_URIS);
+    if (thriftUris == null || thriftUris.isEmpty()) {
+      throw new IllegalArgumentException("metastore.thrift.uris must be 
configured to connect to HMS");
+    }
+
+    LOG.info("Hadoop Configuration initialized");
+    LOG.info("  HMS Thrift URIs: {}", thriftUris);
+
+    if (LOG.isInfoEnabled()) {
+      LOG.info("  Warehouse: {}", MetastoreConf.getVar(conf, 
ConfVars.WAREHOUSE));
+      LOG.info("  Warehouse (external): {}", MetastoreConf.getVar(conf, 
ConfVars.WAREHOUSE_EXTERNAL));
+    }
+  }
+
+  @EventListener
+  public void onWebServerInitialized(WebServerInitializedEvent event) {
+    int actualPort = event.getWebServer().getPort();
+
+    if (actualPort > 0) {
+      port = actualPort;
+      String servletPath = MetastoreConf.getVar(conf, 
ConfVars.ICEBERG_CATALOG_SERVLET_PATH);
+
+      if (servletPath == null || servletPath.isEmpty()) {
+        servletPath = IcebergCatalogConfiguration.DEFAULT_SERVLET_PATH;
+      }
+
+      restEndpoint = UriComponentsBuilder.newInstance()
+          .scheme(isSslEnabled() ? "https" : "http")
+          .host("localhost")
+          .port(actualPort)
+          .pathSegment(servletPath)
+          .toUriString();
+
+      LOG.info("REST endpoint set to actual server port: {}", restEndpoint);
+    }
+  }
+
+  private boolean isSslEnabled() {
+    Ssl ssl = serverProperties != null ? serverProperties.getSsl() : null;
+    return ssl != null && ssl.isEnabled();
+  }
+
+  @VisibleForTesting
+  public int getPort() {
+    return port;
+  }
+
+  public String getRestEndpoint() {
+    return restEndpoint;
+  }
+}
diff --git 
a/standalone-metastore/metastore-rest-catalog/src/main/java/org/apache/iceberg/rest/standalone/StandaloneRESTCatalogServer.java
 
b/standalone-metastore/metastore-rest-catalog/src/main/java/org/apache/iceberg/rest/standalone/StandaloneRESTCatalogServer.java
index 79c89b2cae8..d1f2d87f2f6 100644
--- 
a/standalone-metastore/metastore-rest-catalog/src/main/java/org/apache/iceberg/rest/standalone/StandaloneRESTCatalogServer.java
+++ 
b/standalone-metastore/metastore-rest-catalog/src/main/java/org/apache/iceberg/rest/standalone/StandaloneRESTCatalogServer.java
@@ -17,192 +17,57 @@
  */
 package org.apache.iceberg.rest.standalone;
 
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import com.google.common.annotations.VisibleForTesting;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.hive.metastore.ServletServerBuilder;
 import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
 import org.apache.hadoop.hive.metastore.conf.MetastoreConf.ConfVars;
-import org.apache.iceberg.rest.HMSCatalogFactory;
-import org.eclipse.jetty.server.Server;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
 
 /**
- * Standalone REST Catalog Server.
- * 
+ * Standalone REST Catalog Server with Spring Boot.
+ *
  * <p>This server runs independently of HMS and provides a REST API for 
Iceberg catalog operations.
  * It connects to an external HMS instance via Thrift.
- * 
+ *
  * <p>Designed for Kubernetes deployment with load balancer/API gateway in 
front:
  * <pre>
  *   Client → Load Balancer/API Gateway → StandaloneRESTCatalogServer → HMS
  * </pre>
- * 
+ *
  * <p>Multiple instances can run behind a Kubernetes Service for load 
balancing.
  */
+@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
+@SuppressWarnings("java:S1118") // Not a utility class; Spring Boot requires 
instantiation
 public class StandaloneRESTCatalogServer {
   private static final Logger LOG = 
LoggerFactory.getLogger(StandaloneRESTCatalogServer.class);
-  
-  private final Configuration conf;
-  private Server server;
-  private int port;
-  
-  public StandaloneRESTCatalogServer(Configuration conf) {
-    this.conf = conf;
-  }
-  
-  /**
-   * Starts the standalone REST Catalog server.
-   */
-  public void start() {
-    // Validate required configuration
-    String thriftUris = MetastoreConf.getVar(conf, ConfVars.THRIFT_URIS);
-    if (thriftUris == null || thriftUris.isEmpty()) {
-      throw new IllegalArgumentException("metastore.thrift.uris must be 
configured to connect to HMS");
-    }
-    
-    int servletPort = MetastoreConf.getIntVar(conf, 
ConfVars.CATALOG_SERVLET_PORT);
-    String servletPath = MetastoreConf.getVar(conf, 
ConfVars.ICEBERG_CATALOG_SERVLET_PATH);
-    
-    if (servletPath == null || servletPath.isEmpty()) {
-      servletPath = "iceberg"; // Default path
-      MetastoreConf.setVar(conf, ConfVars.ICEBERG_CATALOG_SERVLET_PATH, 
servletPath);
-    }
-    
-    LOG.info("Starting Standalone REST Catalog Server");
-    LOG.info("  HMS Thrift URIs: {}", thriftUris);
-    LOG.info("  Servlet Port: {}", servletPort);
-    LOG.info("  Servlet Path: /{}", servletPath);
-    
-    // Create servlet using factory
-    ServletServerBuilder.Descriptor catalogDescriptor = 
HMSCatalogFactory.createServlet(conf);
-    if (catalogDescriptor == null) {
-      throw new IllegalStateException("Failed to create REST Catalog servlet. 
" +
-          "Check that metastore.catalog.servlet.port and 
metastore.iceberg.catalog.servlet.path are configured.");
-    }
-    
-    // Create health check servlet
-    HealthCheckServlet healthServlet = new HealthCheckServlet();
-    
-    // Build and start server
-    ServletServerBuilder builder = new ServletServerBuilder(conf);
-    builder.addServlet(catalogDescriptor);
-    builder.addServlet(servletPort, "health", healthServlet);
-    
-    server = builder.start(LOG);
-    if (server == null || !server.isStarted()) {
-      // Server failed to start - likely a port conflict
-      throw new IllegalStateException(String.format(
-          "Failed to start REST Catalog server on port %d. Port may already be 
in use. ", servletPort));
-    }
-    
-    // Get actual port (may be auto-assigned)
-    port = catalogDescriptor.getPort();
-    LOG.info("Standalone REST Catalog Server started successfully on port {}", 
port);
-    LOG.info("  REST Catalog endpoint: http://localhost:{}/{}";, port, 
servletPath);
-    LOG.info("  Health check endpoint: http://localhost:{}/health";, port);
-  }
-  
-  /**
-   * Stops the server.
-   */
-  public void stop() {
-    if (server != null && server.isStarted()) {
-      try {
-        LOG.info("Stopping Standalone REST Catalog Server");
-        server.stop();
-        server.join();
-        LOG.info("Standalone REST Catalog Server stopped");
-      } catch (InterruptedException e) {
-        Thread.currentThread().interrupt();
-        LOG.warn("Server stop interrupted", e);
-      } catch (Exception e) {
-        LOG.error("Error stopping server", e);
-      }
-    }
-  }
-
-  /**
-   * Gets the port the server is listening on.
-   * @return the port number
-   */
-  @VisibleForTesting
-  public int getPort() {
-    return port;
-  }
 
-  /**
-   * Gets the REST Catalog endpoint URL.
-   * @return the endpoint URL
-   */
-  public String getRestEndpoint() {
-    String servletPath = MetastoreConf.getVar(conf, 
ConfVars.ICEBERG_CATALOG_SERVLET_PATH);
-    if (servletPath == null || servletPath.isEmpty()) {
-      servletPath = "iceberg";
-    }
-    return "http://localhost:"; + port + "/" + servletPath;
-  }
-  
-  /**
-   * Simple health check servlet for Kubernetes readiness/liveness probes.
-   */
-  private static final class HealthCheckServlet extends HttpServlet {
-    @Override
-    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
-      try {
-        resp.setContentType("application/json");
-        resp.setStatus(HttpServletResponse.SC_OK);
-        resp.getWriter().println("{\"status\":\"healthy\"}");
-      } catch (IOException e) {
-        LOG.warn("Failed to write health check response", e);
-      }
-    }
-  }
-  
   /**
    * Main method for running as a standalone application.
-   * @param args command line arguments
+   * @param args command line arguments (-Dkey=value for configuration)
    */
   public static void main(String[] args) {
-    Configuration conf = MetastoreConf.newMetastoreConf();
-    
-    // Load configuration from command line args or environment
-    // Format: -Dkey=value or use system properties
+    // Apply -D args to system properties so application.yml and Configuration 
bean pick them up
     for (String arg : args) {
       if (arg.startsWith("-D")) {
         String[] kv = arg.substring(2).split("=", 2);
         if (kv.length == 2) {
-          conf.set(kv[0], kv[1]);
+          System.setProperty(kv[0], kv[1]);
         }
       }
     }
-    
-    StandaloneRESTCatalogServer server = new StandaloneRESTCatalogServer(conf);
-    
-    // Add shutdown hook
-    Runtime.getRuntime().addShutdownHook(new Thread(() -> {
-      LOG.info("Shutdown hook triggered");
-      server.stop();
-    }));
-    
-    try {
-      server.start();
-      LOG.info("Server running. Press Ctrl+C to stop.");
-      
-      // Keep server running
-      server.server.join();
-    } catch (InterruptedException e) {
-      Thread.currentThread().interrupt();
-      LOG.warn("Server stop interrupted", e);
-    } catch (Exception e) {
-      LOG.error("Failed to start server", e);
-      System.exit(1);
+    // Sync port from MetastoreConf to Spring's server.port if not already set
+    if (System.getProperty(ConfVars.CATALOG_SERVLET_PORT.getVarname()) == 
null) {
+      int port = MetastoreConf.getIntVar(MetastoreConf.newMetastoreConf(), 
ConfVars.CATALOG_SERVLET_PORT);
+      if (port > 0) {
+        System.setProperty(ConfVars.CATALOG_SERVLET_PORT.getVarname(), 
String.valueOf(port));
+      }
     }
+
+    SpringApplication.run(StandaloneRESTCatalogServer.class, args);
+
+    LOG.info("Standalone REST Catalog Server started successfully");
+    LOG.info("Server running. Press Ctrl+C to stop.");
   }
 }
diff --git 
a/standalone-metastore/metastore-rest-catalog/src/main/java/org/apache/iceberg/rest/standalone/health/HMSReadinessHealthIndicator.java
 
b/standalone-metastore/metastore-rest-catalog/src/main/java/org/apache/iceberg/rest/standalone/health/HMSReadinessHealthIndicator.java
new file mode 100644
index 00000000000..252a3d298b2
--- /dev/null
+++ 
b/standalone-metastore/metastore-rest-catalog/src/main/java/org/apache/iceberg/rest/standalone/health/HMSReadinessHealthIndicator.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.iceberg.rest.standalone.health;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.metastore.HiveMetaStoreClient;
+import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
+import org.apache.hadoop.hive.metastore.conf.MetastoreConf.ConfVars;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.stereotype.Component;
+
+/**
+ * Custom health indicator for HMS connectivity.
+ * Verifies that HMS is reachable via Thrift, not just that configuration is 
present.
+ * Used by Kubernetes readiness probes to determine if the server is ready to 
accept traffic.
+ */
+@Component
+public class HMSReadinessHealthIndicator implements HealthIndicator {
+  private static final Logger LOG = 
LoggerFactory.getLogger(HMSReadinessHealthIndicator.class);
+
+  private final Configuration conf;
+
+  public HMSReadinessHealthIndicator(Configuration conf) {
+    this.conf = conf;
+  }
+
+  @Override
+  public Health health() {
+    String hmsThriftUris = MetastoreConf.getVar(conf, ConfVars.THRIFT_URIS);
+    if (hmsThriftUris == null || hmsThriftUris.isEmpty()) {
+      return Health.down()
+          .withDetail("reason", "HMS Thrift URIs not configured")
+          .build();
+    }
+
+    try (HiveMetaStoreClient client = new HiveMetaStoreClient(conf)) {
+      // Lightweight call to verify HMS is reachable
+      client.getAllDatabases();
+      return Health.up()
+          .withDetail("hmsThriftUris", hmsThriftUris)
+          .withDetail("warehouse", MetastoreConf.getVar(conf, 
ConfVars.WAREHOUSE))
+          .build();
+    } catch (Exception e) {
+      LOG.warn("HMS connectivity check failed: {}", e.getMessage());
+      return Health.down()
+          .withDetail("hmsThriftUris", hmsThriftUris)
+          .withDetail("error", e.getMessage())
+          .build();
+    }
+  }
+}
diff --git 
a/standalone-metastore/metastore-rest-catalog/src/main/resources/application.yml
 
b/standalone-metastore/metastore-rest-catalog/src/main/resources/application.yml
new file mode 100644
index 00000000000..d15c0701e80
--- /dev/null
+++ 
b/standalone-metastore/metastore-rest-catalog/src/main/resources/application.yml
@@ -0,0 +1,69 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Spring Boot Configuration for Standalone HMS REST Catalog Server
+
+# Server configuration
+# Port is set via MetastoreConf.CATALOG_SERVLET_PORT or 
-Dmetastore.catalog.servlet.port
+# SSL is enabled by default with a bundled self-signed cert (dev). Override 
for production:
+#   -Dserver.ssl.key-store=/path/to/keystore.p12 
-Dserver.ssl.key-store-password=secret
+server:
+  port: ${metastore.catalog.servlet.port:8080}
+  shutdown: graceful
+  ssl:
+    enabled: true
+    key-store: classpath:keystore.p12
+    key-store-password: changeit
+    key-store-type: PKCS12
+    key-alias: iceberg
+spring:
+  lifecycle:
+    timeout-per-shutdown-phase: 30s
+
+# Actuator endpoints for Kubernetes
+management:
+  endpoints:
+    web:
+      exposure:
+        include: health,prometheus,info
+  endpoint:
+    health:
+      show-details: always
+      probes:
+        enabled: true
+  health:
+    livenessState:
+      enabled: true
+    readinessState:
+      enabled: true
+  metrics:
+    export:
+      prometheus:
+        enabled: true
+
+# Logging
+logging:
+  level:
+    org.apache.iceberg.rest.standalone: INFO
+    org.apache.hadoop.hive.metastore: INFO
+    org.springframework.boot: WARN
+
+# Application info
+info:
+  app:
+    name: Standalone HMS REST Catalog Server
+    description: Standalone REST Catalog Server for Apache Hive Metastore
+    version: "@project.version@"
diff --git 
a/standalone-metastore/metastore-rest-catalog/src/main/resources/keystore.p12 
b/standalone-metastore/metastore-rest-catalog/src/main/resources/keystore.p12
new file mode 100644
index 00000000000..b5ba6825dad
Binary files /dev/null and 
b/standalone-metastore/metastore-rest-catalog/src/main/resources/keystore.p12 
differ
diff --git 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/auth/jwt/SimpleJWTAuthenticator.java
 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/auth/jwt/SimpleJWTAuthenticator.java
index a6e85def82c..45fc4d337a7 100644
--- 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/auth/jwt/SimpleJWTAuthenticator.java
+++ 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/auth/jwt/SimpleJWTAuthenticator.java
@@ -38,7 +38,10 @@
 
 public class SimpleJWTAuthenticator {
   private static final Logger LOG = 
LoggerFactory.getLogger(SimpleJWTAuthenticator.class.getName());
-  private static final Set<JOSEObjectType> ACCEPTABLE_TYPES = 
Sets.newHashSet(null, JOSEObjectType.JWT);
+  // Accept both traditional "JWT" and RFC 9068 "at+jwt" (access token as JWT) 
- Keycloak and other
+  // OIDC providers may use "at+jwt" when configured for RFC 9068 compliance.
+  private static final Set<JOSEObjectType> ACCEPTABLE_TYPES =
+      Sets.newHashSet(null, JOSEObjectType.JWT, new JOSEObjectType("at+jwt"));
 
   private final JWTValidator validator;
 
diff --git a/standalone-metastore/pom.xml b/standalone-metastore/pom.xml
index b1045ec5465..abf94901df3 100644
--- a/standalone-metastore/pom.xml
+++ b/standalone-metastore/pom.xml
@@ -43,6 +43,7 @@
     <maven.compiler.source>21</maven.compiler.source>
     <maven.compiler.target>21</maven.compiler.target>
     <java.version>21</java.version>
+    <jakarta.annotation.version>2.1.1</jakarta.annotation.version>
     
<maven.compiler.useIncrementalCompilation>false</maven.compiler.useIncrementalCompilation>
     <maven.cyclonedx.plugin.version>2.7.10</maven.cyclonedx.plugin.version>
     <maven.repo.local>${settings.localRepository}</maven.repo.local>
@@ -126,6 +127,7 @@
     <keycloak.version>26.0.6</keycloak.version>
     <!-- If upgrading, upgrade atlas as well in ql/pom.xml, which brings in 
some springframework dependencies transitively -->
     <spring.version>5.3.39</spring.version>
+    <spring-boot.version>2.7.18</spring-boot.version>
     <spring.ldap.version>2.4.4</spring.ldap.version>
     <testcontainers.version>1.21.3</testcontainers.version>
     <!-- Thrift properties -->
@@ -578,6 +580,86 @@
         <artifactId>testcontainers</artifactId>
         <version>${testcontainers.version}</version>
       </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-servlets</artifactId>
+        <version>${jetty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-webapp</artifactId>
+        <version>${jetty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-continuation</artifactId>
+        <version>${jetty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-xml</artifactId>
+        <version>${jetty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-annotations</artifactId>
+        <version>${jetty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-plus</artifactId>
+        <version>${jetty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-client</artifactId>
+        <version>${jetty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.websocket</groupId>
+        <artifactId>websocket-server</artifactId>
+        <version>${jetty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.websocket</groupId>
+        <artifactId>websocket-common</artifactId>
+        <version>${jetty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.websocket</groupId>
+        <artifactId>websocket-client</artifactId>
+        <version>${jetty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.websocket</groupId>
+        <artifactId>websocket-servlet</artifactId>
+        <version>${jetty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.websocket</groupId>
+        <artifactId>javax-websocket-server-impl</artifactId>
+        <version>${jetty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.websocket</groupId>
+        <artifactId>javax-websocket-client-impl</artifactId>
+        <version>${jetty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-web</artifactId>
+        <version>${spring-boot.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-jetty</artifactId>
+        <version>${spring-boot.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-actuator</artifactId>
+        <version>${spring-boot.version}</version>
+      </dependency>
     </dependencies>
   </dependencyManagement>
   <dependencies>

Reply via email to