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

jshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new 116e5ae15 [#4107] feat(all): Add testConnection API for catalog (#4108)
116e5ae15 is described below

commit 116e5ae1591feca15b8eef20b3d77fcb3a99e8a8
Author: mchades <[email protected]>
AuthorDate: Tue Jul 16 18:54:29 2024 +0800

    [#4107] feat(all): Add testConnection API for catalog (#4108)
    
    ### What changes were proposed in this pull request?
    
    Add testConnection API for catalog
    
    ### Why are the changes needed?
    
    Fix: #4107
    
    ### Does this PR introduce _any_ user-facing change?
    
    yes, add a new API
    
    ### How was this patch tested?
    
    tests added
---
 .../org/apache/gravitino/SupportsCatalogs.java     |  18 ++++
 .../exceptions/ConnectionFailedException.java      |  49 +++++++++
 build.gradle.kts                                   |   4 +-
 .../catalog/hadoop/HadoopCatalogOperations.java    |  21 ++++
 .../hadoop/TestHadoopCatalogOperations.java        |  14 +++
 .../hadoop/integration/test/HadoopCatalogIT.java   |   3 +-
 .../test/HadoopUserImpersonationIT.java            |   3 +-
 .../catalog/hive/HiveCatalogOperations.java        |  27 +++++
 .../catalog/hive/TestHiveCatalogOperations.java    |  28 +++++
 .../catalog/jdbc/JdbcCatalogOperations.java        |  20 ++++
 .../jdbc/converter/JdbcExceptionConverter.java     |   4 +-
 .../catalog/jdbc/TestJdbcCatalogOperations.java    |  54 ++++++++++
 .../jdbc/operation/SqliteDatabaseOperations.java   |  16 ++-
 .../catalog/kafka/KafkaCatalogOperations.java      |  23 +++-
 .../catalog/kafka/TestKafkaCatalogOperations.java  |  15 +++
 .../kafka/integration/test/CatalogKafkaIT.java     |  28 ++++-
 .../iceberg/IcebergCatalogOperations.java          |  26 +++++
 .../iceberg/TestIcebergCatalogOperations.java      |  45 ++++++++
 .../lakehouse/paimon/PaimonCatalogOperations.java  |  25 +++++
 .../lakehouse/paimon/TestPaimonCatalog.java        |  13 +++
 .../org/apache/gravitino/client/ErrorHandlers.java |   4 +
 .../apache/gravitino/client/GravitinoClient.java   |  22 ++++
 .../apache/gravitino/client/GravitinoMetalake.java |  43 ++++++++
 .../gravitino/client/TestGravitinoClient.java      |  65 ++++++++++++
 .../gravitino/dto/responses/ErrorConstants.java    |   3 +
 .../gravitino/dto/responses/ErrorResponse.java     |  26 +++++
 .../org/apache/gravitino/StringIdentifier.java     |   3 +
 .../apache/gravitino/catalog/CatalogManager.java   | 116 +++++++++++++++++----
 .../catalog/CatalogNormalizeDispatcher.java        |  12 +++
 .../apache/gravitino/catalog/SupportsCatalogs.java |  18 ++++
 .../gravitino/connector/CatalogOperations.java     |  20 ++++
 .../gravitino/listener/CatalogEventDispatcher.java |  12 +++
 .../gravitino/catalog/DummyCatalogOperations.java  |  11 ++
 .../gravitino/catalog/TestCatalogManager.java      |  22 +++-
 .../gravitino/connector/TestCatalogOperations.java |  16 +++
 docs/open-api/catalogs.yaml                        |  70 +++++++++++++
 docs/open-api/openapi.yaml                         |   3 +
 .../integration/test/client/CatalogIT.java         |  20 +++-
 .../integration/test/client/MetalakeIT.java        |   3 +-
 .../org/apache/gravitino/server/web/Utils.java     |  14 +++
 .../server/web/rest/CatalogOperations.java         |  39 +++++++
 .../server/web/rest/ExceptionHandlers.java         |  31 ++++++
 .../org/apache/gravitino/server/web/TestUtils.java |  10 ++
 .../gravitino/server/web/rest/TestCatalog.java     |  10 ++
 .../server/web/rest/TestCatalogOperations.java     |  55 ++++++++--
 45 files changed, 1038 insertions(+), 46 deletions(-)

diff --git a/api/src/main/java/org/apache/gravitino/SupportsCatalogs.java 
b/api/src/main/java/org/apache/gravitino/SupportsCatalogs.java
index c3805edec..8644430bc 100644
--- a/api/src/main/java/org/apache/gravitino/SupportsCatalogs.java
+++ b/api/src/main/java/org/apache/gravitino/SupportsCatalogs.java
@@ -114,4 +114,22 @@ public interface SupportsCatalogs {
    * @return True if the catalog was dropped, false otherwise.
    */
   boolean dropCatalog(String catalogName);
+
+  /**
+   * Test whether the catalog with specified parameters can be connected to 
before creating it.
+   *
+   * @param catalogName the name of the catalog.
+   * @param type the type of the catalog.
+   * @param provider the provider of the catalog.
+   * @param comment the comment of the catalog.
+   * @param properties the properties of the catalog.
+   * @throws Exception if the test failed.
+   */
+  void testConnection(
+      String catalogName,
+      Catalog.Type type,
+      String provider,
+      String comment,
+      Map<String, String> properties)
+      throws Exception;
 }
diff --git 
a/api/src/main/java/org/apache/gravitino/exceptions/ConnectionFailedException.java
 
b/api/src/main/java/org/apache/gravitino/exceptions/ConnectionFailedException.java
new file mode 100644
index 000000000..711a3ea2f
--- /dev/null
+++ 
b/api/src/main/java/org/apache/gravitino/exceptions/ConnectionFailedException.java
@@ -0,0 +1,49 @@
+/*
+ * 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.gravitino.exceptions;
+
+import com.google.errorprone.annotations.FormatMethod;
+import com.google.errorprone.annotations.FormatString;
+
+/** An exception thrown when connect to catalog failed. */
+public class ConnectionFailedException extends GravitinoRuntimeException {
+  /**
+   * Constructs a new exception with the specified detail message.
+   *
+   * @param cause the cause.
+   * @param errorMessageTemplate the detail message.
+   * @param args the arguments to the message.
+   */
+  @FormatMethod
+  public ConnectionFailedException(
+      Throwable cause, @FormatString String errorMessageTemplate, Object... 
args) {
+    super(cause, errorMessageTemplate, args);
+  }
+
+  /**
+   * Constructs a new exception with the specified detail message.
+   *
+   * @param errorMessageTemplate the detail message.
+   * @param args the arguments to the message.
+   */
+  @FormatMethod
+  public ConnectionFailedException(@FormatString String errorMessageTemplate, 
Object... args) {
+    super(errorMessageTemplate, args);
+  }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index ead9f0167..7db69dcd5 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -490,7 +490,9 @@ tasks.rat {
     "clients/client-python/.venv/*",
     "clients/client-python/gravitino.egg-info/*",
     "clients/client-python/gravitino/utils/exceptions.py",
-    "clients/client-python/gravitino/utils/http_client.py"
+    "clients/client-python/gravitino/utils/http_client.py",
+    "clients/client-python/tests/unittests/htmlcov/*",
+    "clients/client-python/tests/integration/htmlcov/*"
   )
 
   // Add .gitignore excludes to the Apache Rat exclusion list.
diff --git 
a/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/HadoopCatalogOperations.java
 
b/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/HadoopCatalogOperations.java
index eb77ac4fb..b82eaa359 100644
--- 
a/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/HadoopCatalogOperations.java
+++ 
b/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/HadoopCatalogOperations.java
@@ -34,6 +34,7 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.Catalog;
 import org.apache.gravitino.Entity;
 import org.apache.gravitino.EntityStore;
 import org.apache.gravitino.GravitinoEnv;
@@ -538,6 +539,26 @@ public class HadoopCatalogOperations implements 
CatalogOperations, SupportsSchem
     }
   }
 
+  /**
+   * Since the Hadoop catalog was completely managed by Gravitino, we don't 
need to test the
+   * connection
+   *
+   * @param catalogIdent the name of the catalog.
+   * @param type the type of the catalog.
+   * @param provider the provider of the catalog.
+   * @param comment the comment of the catalog.
+   * @param properties the properties of the catalog.
+   */
+  @Override
+  public void testConnection(
+      NameIdentifier catalogIdent,
+      Catalog.Type type,
+      String provider,
+      String comment,
+      Map<String, String> properties) {
+    // Do nothing
+  }
+
   @Override
   public void close() throws IOException {}
 
diff --git 
a/catalogs/catalog-hadoop/src/test/java/org/apache/gravitino/catalog/hadoop/TestHadoopCatalogOperations.java
 
b/catalogs/catalog-hadoop/src/test/java/org/apache/gravitino/catalog/hadoop/TestHadoopCatalogOperations.java
index 73b0c5dfc..284070f0b 100644
--- 
a/catalogs/catalog-hadoop/src/test/java/org/apache/gravitino/catalog/hadoop/TestHadoopCatalogOperations.java
+++ 
b/catalogs/catalog-hadoop/src/test/java/org/apache/gravitino/catalog/hadoop/TestHadoopCatalogOperations.java
@@ -50,6 +50,7 @@ import java.util.UUID;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.commons.io.FileUtils;
+import org.apache.gravitino.Catalog;
 import org.apache.gravitino.Config;
 import org.apache.gravitino.EntityStore;
 import org.apache.gravitino.EntityStoreFactory;
@@ -703,6 +704,19 @@ public class TestHadoopCatalogOperations {
     }
   }
 
+  @Test
+  public void testTestConnection() {
+    HadoopCatalogOperations catalogOperations = new 
HadoopCatalogOperations(store);
+    Assertions.assertDoesNotThrow(
+        () ->
+            catalogOperations.testConnection(
+                NameIdentifier.of("metalake", "catalog"),
+                Catalog.Type.FILESET,
+                "hadoop",
+                "comment",
+                ImmutableMap.of()));
+  }
+
   private static Stream<Arguments> locationArguments() {
     return Stream.of(
         // Honor the catalog location
diff --git 
a/catalogs/catalog-hadoop/src/test/java/org/apache/gravitino/catalog/hadoop/integration/test/HadoopCatalogIT.java
 
b/catalogs/catalog-hadoop/src/test/java/org/apache/gravitino/catalog/hadoop/integration/test/HadoopCatalogIT.java
index 35786080a..3079e9203 100644
--- 
a/catalogs/catalog-hadoop/src/test/java/org/apache/gravitino/catalog/hadoop/integration/test/HadoopCatalogIT.java
+++ 
b/catalogs/catalog-hadoop/src/test/java/org/apache/gravitino/catalog/hadoop/integration/test/HadoopCatalogIT.java
@@ -31,6 +31,7 @@ import org.apache.gravitino.Namespace;
 import org.apache.gravitino.Schema;
 import org.apache.gravitino.client.GravitinoMetalake;
 import org.apache.gravitino.exceptions.FilesetAlreadyExistsException;
+import org.apache.gravitino.exceptions.IllegalNameIdentifierException;
 import org.apache.gravitino.exceptions.NoSuchFilesetException;
 import org.apache.gravitino.file.Fileset;
 import org.apache.gravitino.file.FilesetChange;
@@ -187,7 +188,7 @@ public class HadoopCatalogIT extends AbstractIT {
 
     // create fileset with null fileset name
     Assertions.assertThrows(
-        IllegalArgumentException.class,
+        IllegalNameIdentifierException.class,
         () ->
             createFileset(
                 null,
diff --git 
a/catalogs/catalog-hadoop/src/test/java/org/apache/gravitino/catalog/hadoop/integration/test/HadoopUserImpersonationIT.java
 
b/catalogs/catalog-hadoop/src/test/java/org/apache/gravitino/catalog/hadoop/integration/test/HadoopUserImpersonationIT.java
index 5edab4766..248b8d54f 100644
--- 
a/catalogs/catalog-hadoop/src/test/java/org/apache/gravitino/catalog/hadoop/integration/test/HadoopUserImpersonationIT.java
+++ 
b/catalogs/catalog-hadoop/src/test/java/org/apache/gravitino/catalog/hadoop/integration/test/HadoopUserImpersonationIT.java
@@ -43,6 +43,7 @@ import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Schema;
 import org.apache.gravitino.client.GravitinoMetalake;
 import org.apache.gravitino.exceptions.FilesetAlreadyExistsException;
+import org.apache.gravitino.exceptions.IllegalNameIdentifierException;
 import org.apache.gravitino.file.Fileset;
 import org.apache.gravitino.integration.test.util.AbstractIT;
 import org.apache.gravitino.integration.test.util.GravitinoITUtils;
@@ -398,7 +399,7 @@ public class HadoopUserImpersonationIT extends AbstractIT {
 
     // create fileset with null fileset name
     Assertions.assertThrows(
-        IllegalArgumentException.class,
+        IllegalNameIdentifierException.class,
         () ->
             createFileset(
                 null,
diff --git 
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveCatalogOperations.java
 
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveCatalogOperations.java
index 15357715e..2036a6b51 100644
--- 
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveCatalogOperations.java
+++ 
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveCatalogOperations.java
@@ -55,6 +55,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.Catalog;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.SchemaChange;
@@ -64,6 +65,7 @@ import org.apache.gravitino.connector.CatalogOperations;
 import org.apache.gravitino.connector.HasPropertyMetadata;
 import org.apache.gravitino.connector.ProxyPlugin;
 import org.apache.gravitino.connector.SupportsSchemas;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
@@ -85,6 +87,7 @@ import org.apache.gravitino.rel.indexes.Index;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hive.conf.HiveConf;
 import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
+import org.apache.hadoop.hive.metastore.IMetaStoreClient;
 import org.apache.hadoop.hive.metastore.api.AlreadyExistsException;
 import org.apache.hadoop.hive.metastore.api.Database;
 import org.apache.hadoop.hive.metastore.api.FieldSchema;
@@ -1091,6 +1094,30 @@ public class HiveCatalogOperations implements 
CatalogOperations, SupportsSchemas
     }
   }
 
+  /**
+   * Performs `getAllDatabases` operation in Hive Metastore to test the 
connection.
+   *
+   * @param catalogIdent the name of the catalog.
+   * @param type the type of the catalog.
+   * @param provider the provider of the catalog.
+   * @param comment the comment of the catalog.
+   * @param properties the properties of the catalog.
+   */
+  @Override
+  public void testConnection(
+      NameIdentifier catalogIdent,
+      Catalog.Type type,
+      String provider,
+      String comment,
+      Map<String, String> properties) {
+    try {
+      clientPool.run(IMetaStoreClient::getAllDatabases);
+    } catch (Exception e) {
+      throw new ConnectionFailedException(
+          e, "Failed to run getAllDatabases in Hive Metastore: %s", 
e.getMessage());
+    }
+  }
+
   /**
    * Checks if the given namespace is a valid namespace for the Hive schema.
    *
diff --git 
a/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalogOperations.java
 
b/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalogOperations.java
index 256492880..e78d81ad0 100644
--- 
a/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalogOperations.java
+++ 
b/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalogOperations.java
@@ -32,13 +32,20 @@ import static 
org.apache.gravitino.catalog.hive.HiveCatalogPropertiesMeta.METAST
 import static 
org.apache.gravitino.catalog.hive.HiveCatalogPropertiesMeta.PRINCIPAL;
 import static 
org.apache.gravitino.catalog.hive.TestHiveCatalog.HIVE_PROPERTIES_METADATA;
 import static org.apache.gravitino.connector.BaseCatalog.CATALOG_BYPASS_PREFIX;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import java.util.Map;
 import org.apache.gravitino.Catalog;
+import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.connector.BaseCatalog;
 import org.apache.gravitino.connector.PropertyEntry;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
 import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
+import org.apache.thrift.TException;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -126,4 +133,25 @@ class TestHiveCatalogOperations {
     Assertions.assertEquals("v2", op.hiveConf.get("a.b"));
     Assertions.assertEquals("v4", op.hiveConf.get("c.d"));
   }
+
+  @Test
+  void testTestConnection() throws TException, InterruptedException {
+    HiveCatalogOperations op = new HiveCatalogOperations();
+    op.clientPool = mock(CachedClientPool.class);
+    when(op.clientPool.run(any())).thenThrow(new TException("mock connection 
exception"));
+
+    ConnectionFailedException exception =
+        Assertions.assertThrows(
+            ConnectionFailedException.class,
+            () ->
+                op.testConnection(
+                    NameIdentifier.of("metalake", "catalog"),
+                    Catalog.Type.RELATIONAL,
+                    "hive",
+                    "comment",
+                    ImmutableMap.of()));
+    Assertions.assertEquals(
+        "Failed to run getAllDatabases in Hive Metastore: mock connection 
exception",
+        exception.getMessage());
+  }
 }
diff --git 
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/JdbcCatalogOperations.java
 
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/JdbcCatalogOperations.java
index 610a556f6..8961df99e 100644
--- 
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/JdbcCatalogOperations.java
+++ 
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/JdbcCatalogOperations.java
@@ -36,6 +36,7 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import javax.sql.DataSource;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.Catalog;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.SchemaChange;
@@ -173,6 +174,25 @@ public class JdbcCatalogOperations implements 
CatalogOperations, SupportsSchemas
         .toArray(NameIdentifier[]::new);
   }
 
+  /**
+   * Performs `show databases` operation to check if the JDBC connection is 
valid.
+   *
+   * @param catalogIdent the name of the catalog.
+   * @param type the type of the catalog.
+   * @param provider the provider of the catalog.
+   * @param comment the comment of the catalog.
+   * @param properties the properties of the catalog.
+   */
+  @Override
+  public void testConnection(
+      NameIdentifier catalogIdent,
+      Catalog.Type type,
+      String provider,
+      String comment,
+      Map<String, String> properties) {
+    databaseOperation.listDatabases();
+  }
+
   /**
    * Creates a new schema with the provided identifier, comment and metadata.
    *
diff --git 
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/converter/JdbcExceptionConverter.java
 
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/converter/JdbcExceptionConverter.java
index be50433a6..7d8e5cc92 100644
--- 
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/converter/JdbcExceptionConverter.java
+++ 
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/converter/JdbcExceptionConverter.java
@@ -28,8 +28,8 @@ public abstract class JdbcExceptionConverter {
    * Convert JDBC exception to GravitinoException.
    *
    * @param sqlException The sql exception to map
-   * @return A best attempt at a corresponding connector exception or generic 
with the SQLException
-   *     as the cause
+   * @return The best attempt at a corresponding connector exception or 
generic with the
+   *     SQLException as the cause
    */
   @SuppressWarnings("FormatStringAnnotation")
   public GravitinoRuntimeException toGravitinoException(final SQLException 
sqlException) {
diff --git 
a/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/TestJdbcCatalogOperations.java
 
b/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/TestJdbcCatalogOperations.java
new file mode 100644
index 000000000..aab5760f9
--- /dev/null
+++ 
b/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/TestJdbcCatalogOperations.java
@@ -0,0 +1,54 @@
+/*
+ * 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.gravitino.catalog.jdbc;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.NameIdentifier;
+import 
org.apache.gravitino.catalog.jdbc.converter.SqliteColumnDefaultValueConverter;
+import org.apache.gravitino.catalog.jdbc.converter.SqliteExceptionConverter;
+import org.apache.gravitino.catalog.jdbc.converter.SqliteTypeConverter;
+import org.apache.gravitino.catalog.jdbc.operation.SqliteDatabaseOperations;
+import org.apache.gravitino.catalog.jdbc.operation.SqliteTableOperations;
+import org.apache.gravitino.exceptions.GravitinoRuntimeException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestJdbcCatalogOperations {
+
+  @Test
+  public void testTestConnection() {
+    JdbcCatalogOperations catalogOperations =
+        new JdbcCatalogOperations(
+            new SqliteExceptionConverter(),
+            new SqliteTypeConverter(),
+            new SqliteDatabaseOperations("/illegal/path"),
+            new SqliteTableOperations(),
+            new SqliteColumnDefaultValueConverter());
+    Assertions.assertThrows(
+        GravitinoRuntimeException.class,
+        () ->
+            catalogOperations.testConnection(
+                NameIdentifier.of("metalake", "catalog"),
+                Catalog.Type.RELATIONAL,
+                "sqlite",
+                "comment",
+                ImmutableMap.of()));
+  }
+}
diff --git 
a/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/operation/SqliteDatabaseOperations.java
 
b/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/operation/SqliteDatabaseOperations.java
index f65c585af..282f1f48b 100644
--- 
a/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/operation/SqliteDatabaseOperations.java
+++ 
b/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/operation/SqliteDatabaseOperations.java
@@ -32,6 +32,7 @@ import org.apache.commons.io.FileUtils;
 import org.apache.gravitino.catalog.jdbc.JdbcSchema;
 import org.apache.gravitino.catalog.jdbc.converter.SqliteExceptionConverter;
 import org.apache.gravitino.catalog.jdbc.utils.JdbcConnectorUtils;
+import org.apache.gravitino.exceptions.GravitinoRuntimeException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.SchemaAlreadyExistsException;
 import org.apache.gravitino.meta.AuditInfo;
@@ -70,11 +71,16 @@ public class SqliteDatabaseOperations extends 
JdbcDatabaseOperations {
 
   @Override
   public List<String> listDatabases() {
-    File file = new File(dbPath);
-    Preconditions.checkArgument(file.exists(), "Database path %s does not 
exist", dbPath);
-    return Arrays.stream(Objects.requireNonNull(file.listFiles()))
-        .map(File::getName)
-        .collect(Collectors.toList());
+    try {
+      File file = new File(dbPath);
+      Preconditions.checkArgument(file.exists(), "Database path %s does not 
exist", dbPath);
+      return Arrays.stream(Objects.requireNonNull(file.listFiles()))
+          .map(File::getName)
+          .collect(Collectors.toList());
+    } catch (Exception e) {
+      throw new GravitinoRuntimeException(
+          e, "Failed to list databases in %s: %s", dbPath, e.getMessage());
+    }
   }
 
   @Override
diff --git 
a/catalogs/catalog-kafka/src/main/java/org/apache/gravitino/catalog/kafka/KafkaCatalogOperations.java
 
b/catalogs/catalog-kafka/src/main/java/org/apache/gravitino/catalog/kafka/KafkaCatalogOperations.java
index d78761262..d3901218d 100644
--- 
a/catalogs/catalog-kafka/src/main/java/org/apache/gravitino/catalog/kafka/KafkaCatalogOperations.java
+++ 
b/catalogs/catalog-kafka/src/main/java/org/apache/gravitino/catalog/kafka/KafkaCatalogOperations.java
@@ -18,6 +18,7 @@
  */
 package org.apache.gravitino.catalog.kafka;
 
+import static org.apache.gravitino.StringIdentifier.DUMMY_ID;
 import static org.apache.gravitino.StringIdentifier.ID_KEY;
 import static org.apache.gravitino.StringIdentifier.newPropertiesWithId;
 import static org.apache.gravitino.connector.BaseCatalog.CATALOG_BYPASS_PREFIX;
@@ -38,6 +39,7 @@ import java.util.Properties;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
+import org.apache.gravitino.Catalog;
 import org.apache.gravitino.Entity;
 import org.apache.gravitino.EntityStore;
 import org.apache.gravitino.GravitinoEnv;
@@ -50,6 +52,7 @@ import org.apache.gravitino.connector.CatalogInfo;
 import org.apache.gravitino.connector.CatalogOperations;
 import org.apache.gravitino.connector.HasPropertyMetadata;
 import org.apache.gravitino.connector.SupportsSchemas;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchEntityException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
@@ -181,6 +184,21 @@ public class KafkaCatalogOperations implements 
CatalogOperations, SupportsSchema
     }
   }
 
+  @Override
+  public void testConnection(
+      NameIdentifier catalogIdent,
+      Catalog.Type type,
+      String provider,
+      String comment,
+      Map<String, String> properties) {
+    try {
+      adminClient.listTopics().names().get();
+    } catch (Exception e) {
+      throw new ConnectionFailedException(
+          e, "Failed to run listTopics in Kafka: %s", e.getMessage());
+    }
+  }
+
   @Override
   public Topic loadTopic(NameIdentifier ident) throws NoSuchTopicException {
     NameIdentifier schemaIdent = NameIdentifier.of(ident.namespace().levels());
@@ -552,9 +570,10 @@ public class KafkaCatalogOperations implements 
CatalogOperations, SupportsSchema
   }
 
   private void createDefaultSchemaIfNecessary() {
-    // If the default schema already exists, do nothing
+    // If the default schema already exists or is testConnection operation, do 
nothing
     try {
-      if (store.exists(defaultSchemaIdent, Entity.EntityType.SCHEMA)) {
+      if (DUMMY_ID.toString().equals(info.properties().get(ID_KEY))
+          || store.exists(defaultSchemaIdent, Entity.EntityType.SCHEMA)) {
         return;
       }
     } catch (IOException e) {
diff --git 
a/catalogs/catalog-kafka/src/test/java/org/apache/gravitino/catalog/kafka/TestKafkaCatalogOperations.java
 
b/catalogs/catalog-kafka/src/test/java/org/apache/gravitino/catalog/kafka/TestKafkaCatalogOperations.java
index 8826e2804..58cd55042 100644
--- 
a/catalogs/catalog-kafka/src/test/java/org/apache/gravitino/catalog/kafka/TestKafkaCatalogOperations.java
+++ 
b/catalogs/catalog-kafka/src/test/java/org/apache/gravitino/catalog/kafka/TestKafkaCatalogOperations.java
@@ -186,6 +186,7 @@ public class TestKafkaCatalogOperations extends 
KafkaClusterEmbedded {
             .withNamespace(Namespace.of(METALAKE_NAME))
             .withType(MESSAGING)
             .withProvider("kafka")
+            .withProperties(MOCK_CATALOG_PROPERTIES)
             .withAuditInfo(
                 AuditInfo.builder()
                     .withCreator("testKafkaUser")
@@ -221,6 +222,7 @@ public class TestKafkaCatalogOperations extends 
KafkaClusterEmbedded {
                     .withCreator("testKafkaUser")
                     .withCreateTime(Instant.now())
                     .build())
+            .withProperties(MOCK_CATALOG_PROPERTIES)
             .build();
     KafkaCatalogOperations ops = new KafkaCatalogOperations(store, 
idGenerator);
     Assertions.assertNull(ops.adminClientConfig);
@@ -256,6 +258,7 @@ public class TestKafkaCatalogOperations extends 
KafkaClusterEmbedded {
                     .withCreator("testKafkaUser")
                     .withCreateTime(Instant.now())
                     .build())
+            .withProperties(MOCK_CATALOG_PROPERTIES)
             .build();
     KafkaCatalogOperations ops = new KafkaCatalogOperations(store, 
idGenerator);
     ops.initialize(
@@ -544,4 +547,16 @@ public class TestKafkaCatalogOperations extends 
KafkaClusterEmbedded {
                     ident, TopicChange.setProperty(PARTITION_COUNT, "1")));
     Assertions.assertEquals("Cannot reduce partition count from 3 to 1", 
exception.getMessage());
   }
+
+  @Test
+  public void testTestConnection() {
+    Assertions.assertDoesNotThrow(
+        () ->
+            kafkaCatalogOperations.testConnection(
+                NameIdentifier.of("metalake", "catalog"),
+                MESSAGING,
+                "kafka",
+                "comment",
+                ImmutableMap.of()));
+  }
 }
diff --git 
a/catalogs/catalog-kafka/src/test/java/org/apache/gravitino/catalog/kafka/integration/test/CatalogKafkaIT.java
 
b/catalogs/catalog-kafka/src/test/java/org/apache/gravitino/catalog/kafka/integration/test/CatalogKafkaIT.java
index 0f580170d..88e1df80c 100644
--- 
a/catalogs/catalog-kafka/src/test/java/org/apache/gravitino/catalog/kafka/integration/test/CatalogKafkaIT.java
+++ 
b/catalogs/catalog-kafka/src/test/java/org/apache/gravitino/catalog/kafka/integration/test/CatalogKafkaIT.java
@@ -139,11 +139,18 @@ public class CatalogKafkaIT extends AbstractIT {
 
   @Test
   public void testCatalog() throws ExecutionException, InterruptedException {
-    // test create catalog
     String catalogName = GravitinoITUtils.genRandomName("test_catalog");
     String comment = "test catalog";
     Map<String, String> properties =
         ImmutableMap.of(BOOTSTRAP_SERVERS, kafkaBootstrapServers, "key1", 
"value1");
+
+    // test before creation
+    Assertions.assertDoesNotThrow(
+        () ->
+            metalake.testConnection(
+                catalogName, Catalog.Type.MESSAGING, PROVIDER, comment, 
properties));
+
+    // test create catalog
     Catalog createdCatalog = createCatalog(catalogName, comment, properties);
     Assertions.assertEquals(catalogName, createdCatalog.name());
     Assertions.assertEquals(comment, createdCatalog.comment());
@@ -190,6 +197,23 @@ public class CatalogKafkaIT extends AbstractIT {
             IllegalArgumentException.class, () -> 
catalog1.asSchemas().listSchemas());
     Assertions.assertTrue(exception.getMessage().contains("Invalid url in 
bootstrap.servers: 2"));
 
+    // test before creation
+    ImmutableMap<String, String> illegalProps = ImmutableMap.of("abc", "2");
+    exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                metalake.testConnection(
+                    GravitinoITUtils.genRandomName("test_catalog"),
+                    Catalog.Type.MESSAGING,
+                    PROVIDER,
+                    "comment",
+                    illegalProps));
+    Assertions.assertTrue(
+        exception
+            .getMessage()
+            .contains("Properties are required and must be set: 
[bootstrap.servers]"));
+
     exception =
         Assertions.assertThrows(
             IllegalArgumentException.class,
@@ -199,7 +223,7 @@ public class CatalogKafkaIT extends AbstractIT {
                     Catalog.Type.MESSAGING,
                     PROVIDER,
                     "comment",
-                    ImmutableMap.of("abc", "2")));
+                    illegalProps));
     Assertions.assertTrue(
         exception
             .getMessage()
diff --git 
a/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergCatalogOperations.java
 
b/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergCatalogOperations.java
index 1946bcb81..bf520ba1a 100644
--- 
a/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergCatalogOperations.java
+++ 
b/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergCatalogOperations.java
@@ -33,6 +33,7 @@ import java.util.Optional;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.Catalog;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.SchemaChange;
@@ -42,6 +43,7 @@ import org.apache.gravitino.connector.CatalogInfo;
 import org.apache.gravitino.connector.CatalogOperations;
 import org.apache.gravitino.connector.HasPropertyMetadata;
 import org.apache.gravitino.connector.SupportsSchemas;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
@@ -552,6 +554,30 @@ public class IcebergCatalogOperations implements 
CatalogOperations, SupportsSche
     }
   }
 
+  /**
+   * Performs `listNamespaces` operation on the Iceberg catalog to test the 
connection.
+   *
+   * @param catalogIdent the name of the catalog.
+   * @param type the type of the catalog.
+   * @param provider the provider of the catalog.
+   * @param comment the comment of the catalog.
+   * @param properties the properties of the catalog.
+   */
+  @Override
+  public void testConnection(
+      NameIdentifier catalogIdent,
+      Catalog.Type type,
+      String provider,
+      String comment,
+      Map<String, String> properties) {
+    try {
+      
icebergTableOps.listNamespace(IcebergTableOpsHelper.getIcebergNamespace());
+    } catch (Exception e) {
+      throw new ConnectionFailedException(
+          e, "Failed to run listNamespace on Iceberg catalog: %s", 
e.getMessage());
+    }
+  }
+
   // TODO. We should figure out a better way to get the current user from 
servlet container.
   private static String currentUser() {
     return System.getProperty("user.name");
diff --git 
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalogOperations.java
 
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalogOperations.java
new file mode 100644
index 000000000..06e95e4bd
--- /dev/null
+++ 
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalogOperations.java
@@ -0,0 +1,45 @@
+/*
+ * 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.gravitino.catalog.lakehouse.iceberg;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.exceptions.GravitinoRuntimeException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestIcebergCatalogOperations {
+  @Test
+  public void testTestConnection() {
+    IcebergCatalogOperations catalogOperations = new 
IcebergCatalogOperations();
+    Exception exception =
+        Assertions.assertThrows(
+            GravitinoRuntimeException.class,
+            () ->
+                catalogOperations.testConnection(
+                    NameIdentifier.of("metalake", "catalog"),
+                    Catalog.Type.RELATIONAL,
+                    "iceberg",
+                    "comment",
+                    ImmutableMap.of()));
+    Assertions.assertTrue(
+        exception.getMessage().contains("Failed to run listNamespace on 
Iceberg catalog"));
+  }
+}
diff --git 
a/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonCatalogOperations.java
 
b/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonCatalogOperations.java
index eca0e8b46..225c8e017 100644
--- 
a/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonCatalogOperations.java
+++ 
b/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonCatalogOperations.java
@@ -39,6 +39,7 @@ import org.apache.gravitino.connector.CatalogInfo;
 import org.apache.gravitino.connector.CatalogOperations;
 import org.apache.gravitino.connector.HasPropertyMetadata;
 import org.apache.gravitino.connector.SupportsSchemas;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
@@ -120,6 +121,30 @@ public class PaimonCatalogOperations implements 
CatalogOperations, SupportsSchem
         .toArray(NameIdentifier[]::new);
   }
 
+  /**
+   * Performs `listDatabases` operation on the Paimon catalog to test the 
catalog creation.
+   *
+   * @param catalogIdent the name of the catalog.
+   * @param type the type of the catalog.
+   * @param provider the provider of the catalog.
+   * @param comment the comment of the catalog.
+   * @param properties the properties of the catalog.
+   */
+  @Override
+  public void testConnection(
+      NameIdentifier catalogIdent,
+      org.apache.gravitino.Catalog.Type type,
+      String provider,
+      String comment,
+      Map<String, String> properties) {
+    try {
+      paimonCatalogOps.listDatabases();
+    } catch (Exception e) {
+      throw new ConnectionFailedException(
+          e, "Failed to run listDatabases on Paimon catalog: %s", 
e.getMessage());
+    }
+  }
+
   /**
    * Creates a new schema with the provided identifier, comment, and metadata.
    *
diff --git 
a/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalog.java
 
b/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalog.java
index caede716b..e6439a8d7 100644
--- 
a/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalog.java
+++ 
b/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalog.java
@@ -22,12 +22,15 @@ import static 
org.apache.gravitino.catalog.lakehouse.paimon.PaimonCatalog.CATALO
 import static 
org.apache.gravitino.catalog.lakehouse.paimon.PaimonCatalog.SCHEMA_PROPERTIES_META;
 import static 
org.apache.gravitino.catalog.lakehouse.paimon.PaimonCatalog.TABLE_PROPERTIES_META;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import java.io.File;
 import java.io.IOException;
 import java.time.Instant;
 import java.util.Map;
 import org.apache.commons.io.FileUtils;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.catalog.PropertiesMetadataHelpers;
 import org.apache.gravitino.catalog.lakehouse.paimon.ops.PaimonCatalogOps;
@@ -111,6 +114,16 @@ public class TestPaimonCatalog {
     Assertions.assertEquals(
         paimonCatalogOperations.listSchemas(Namespace.empty()).length,
         paimonCatalogOps.listDatabases().size());
+
+    // test testConnection
+    Assertions.assertDoesNotThrow(
+        () ->
+            paimonCatalogOperations.testConnection(
+                NameIdentifier.of("metalake", "catalog"),
+                Catalog.Type.RELATIONAL,
+                "paimon",
+                "comment",
+                ImmutableMap.of()));
   }
 
   @Test
diff --git 
a/clients/client-java/src/main/java/org/apache/gravitino/client/ErrorHandlers.java
 
b/clients/client-java/src/main/java/org/apache/gravitino/client/ErrorHandlers.java
index 77e581b7c..ae87cf6e1 100644
--- 
a/clients/client-java/src/main/java/org/apache/gravitino/client/ErrorHandlers.java
+++ 
b/clients/client-java/src/main/java/org/apache/gravitino/client/ErrorHandlers.java
@@ -27,6 +27,7 @@ import org.apache.gravitino.dto.responses.ErrorResponse;
 import org.apache.gravitino.dto.responses.OAuth2ErrorResponse;
 import org.apache.gravitino.exceptions.BadRequestException;
 import org.apache.gravitino.exceptions.CatalogAlreadyExistsException;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
 import org.apache.gravitino.exceptions.FilesetAlreadyExistsException;
 import org.apache.gravitino.exceptions.GroupAlreadyExistsException;
 import org.apache.gravitino.exceptions.MetalakeAlreadyExistsException;
@@ -340,6 +341,9 @@ public class ErrorHandlers {
         case ErrorConstants.ILLEGAL_ARGUMENTS_CODE:
           throw new IllegalArgumentException(errorMessage);
 
+        case ErrorConstants.CONNECTION_FAILED_CODE:
+          throw new ConnectionFailedException(errorMessage);
+
         case ErrorConstants.NOT_FOUND_CODE:
           if 
(errorResponse.getType().equals(NoSuchMetalakeException.class.getSimpleName())) 
{
             throw new NoSuchMetalakeException(errorMessage);
diff --git 
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
 
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
index b0311fd7b..1311930b8 100644
--- 
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
+++ 
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
@@ -117,6 +117,28 @@ public class GravitinoClient extends GravitinoClientBase 
implements SupportsCata
     return new ClientBuilder(uri);
   }
 
+  /**
+   * Test whether a catalog can be created successfully with the specified 
parameters, without
+   * actually creating it.
+   *
+   * @param catalogName the name of the catalog.
+   * @param type the type of the catalog.
+   * @param provider the provider of the catalog.
+   * @param comment the comment of the catalog.
+   * @param properties the properties of the catalog.
+   * @throws Exception if the test failed.
+   */
+  @Override
+  public void testConnection(
+      String catalogName,
+      Catalog.Type type,
+      String provider,
+      String comment,
+      Map<String, String> properties)
+      throws Exception {
+    getMetalake().testConnection(catalogName, type, provider, comment, 
properties);
+  }
+
   /** Builder class for constructing a GravitinoClient. */
   public static class ClientBuilder extends 
GravitinoClientBase.Builder<GravitinoClient> {
 
diff --git 
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
 
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
index 1c3276f64..f8a95aa9b 100644
--- 
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
+++ 
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
@@ -39,6 +39,7 @@ import org.apache.gravitino.dto.responses.CatalogListResponse;
 import org.apache.gravitino.dto.responses.CatalogResponse;
 import org.apache.gravitino.dto.responses.DropResponse;
 import org.apache.gravitino.dto.responses.EntityListResponse;
+import org.apache.gravitino.dto.responses.ErrorResponse;
 import org.apache.gravitino.exceptions.CatalogAlreadyExistsException;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
@@ -216,6 +217,48 @@ public class GravitinoMetalake extends MetalakeDTO 
implements SupportsCatalogs {
     return resp.dropped();
   }
 
+  /**
+   * Test whether a catalog can be created successfully with the specified 
parameters, without
+   * actually creating it.
+   *
+   * @param catalogName the name of the catalog.
+   * @param type the type of the catalog.
+   * @param provider the provider of the catalog.
+   * @param comment the comment of the catalog.
+   * @param properties the properties of the catalog.
+   * @throws Exception if the test failed.
+   */
+  @Override
+  public void testConnection(
+      String catalogName,
+      Catalog.Type type,
+      String provider,
+      String comment,
+      Map<String, String> properties)
+      throws Exception {
+    CatalogCreateRequest req =
+        new CatalogCreateRequest(catalogName, type, provider, comment, 
properties);
+    req.validate();
+
+    // The response maybe a `BaseResponse` (test successfully)  or an 
`ErrorResponse` (test failed),
+    // we use the `ErrorResponse` here because it contains all fields of 
`BaseResponse` (code field
+    // only)
+    ErrorResponse resp =
+        restClient.post(
+            String.format("api/metalakes/%s/catalogs/testConnection", 
this.name()),
+            req,
+            ErrorResponse.class,
+            Collections.emptyMap(),
+            ErrorHandlers.catalogErrorHandler());
+
+    if (resp.getCode() == 0) {
+      return;
+    }
+
+    // Throw the corresponding exception
+    ErrorHandlers.catalogErrorHandler().accept(resp);
+  }
+
   static class Builder extends MetalakeDTO.Builder<Builder> {
     private RESTClient restClient;
 
diff --git 
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestGravitinoClient.java
 
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestGravitinoClient.java
index 3aa0e1a4a..4c5cef167 100644
--- 
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestGravitinoClient.java
+++ 
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestGravitinoClient.java
@@ -24,19 +24,23 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.Map;
 import java.util.stream.Collectors;
+import org.apache.gravitino.Catalog;
 import org.apache.gravitino.MetalakeChange;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Version;
 import org.apache.gravitino.dto.AuditDTO;
 import org.apache.gravitino.dto.MetalakeDTO;
 import org.apache.gravitino.dto.VersionDTO;
+import org.apache.gravitino.dto.requests.CatalogCreateRequest;
 import org.apache.gravitino.dto.requests.MetalakeCreateRequest;
 import org.apache.gravitino.dto.requests.MetalakeUpdatesRequest;
+import org.apache.gravitino.dto.responses.BaseResponse;
 import org.apache.gravitino.dto.responses.DropResponse;
 import org.apache.gravitino.dto.responses.ErrorResponse;
 import org.apache.gravitino.dto.responses.MetalakeListResponse;
 import org.apache.gravitino.dto.responses.MetalakeResponse;
 import org.apache.gravitino.dto.responses.VersionResponse;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
 import org.apache.gravitino.exceptions.GravitinoRuntimeException;
 import org.apache.gravitino.exceptions.IllegalNamespaceException;
 import org.apache.gravitino.exceptions.MetalakeAlreadyExistsException;
@@ -338,4 +342,65 @@ public class TestGravitinoClient extends TestBase {
           Assertions.assertEquals(currentVersion.gitCommit, 
version.gitCommit());
         });
   }
+
+  @Test
+  public void testTestConnection() throws Exception {
+    MetalakeDTO mockMetalake =
+        MetalakeDTO.builder()
+            .withName("mock")
+            .withComment("comment")
+            .withAudit(
+                
AuditDTO.builder().withCreator("creator").withCreateTime(Instant.now()).build())
+            .build();
+    MetalakeCreateRequest metalakeReq =
+        new MetalakeCreateRequest("mock", "comment", Collections.emptyMap());
+    MetalakeResponse metalakeResp = new MetalakeResponse(mockMetalake);
+    buildMockResource(Method.POST, "/api/metalakes", metalakeReq, 
metalakeResp, HttpStatus.SC_OK);
+    NameIdentifier id = NameIdentifier.parse("mock");
+    GravitinoMetalake metaLake =
+        client.createMetalake(id.name(), "comment", Collections.emptyMap());
+
+    // test TestConnection success
+    CatalogCreateRequest req =
+        new CatalogCreateRequest(
+            "catalog", Catalog.Type.RELATIONAL, "hive", "comment", 
Collections.emptyMap());
+    BaseResponse resp = new BaseResponse();
+    buildMockResource(
+        Method.POST, "/api/metalakes/mock/catalogs/testConnection", req, resp, 
HttpStatus.SC_OK);
+    Assertions.assertDoesNotThrow(
+        () ->
+            metaLake.testConnection(
+                "catalog", Catalog.Type.RELATIONAL, "hive", "comment", 
Collections.emptyMap()));
+
+    // test TestConnection failed
+    resp = ErrorResponse.illegalArguments("mock error");
+    buildMockResource(
+        Method.POST,
+        "/api/metalakes/mock/catalogs/testConnection",
+        req,
+        resp,
+        HttpStatus.SC_BAD_REQUEST);
+    Exception exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                metaLake.testConnection(
+                    "catalog", Catalog.Type.RELATIONAL, "hive", "comment", 
Collections.emptyMap()));
+    Assertions.assertTrue(exception.getMessage().contains("mock error"));
+
+    resp = ErrorResponse.connectionFailed("connection failed");
+    buildMockResource(
+        Method.POST,
+        "/api/metalakes/mock/catalogs/testConnection",
+        req,
+        resp,
+        HttpStatus.SC_BAD_GATEWAY);
+    exception =
+        Assertions.assertThrows(
+            ConnectionFailedException.class,
+            () ->
+                metaLake.testConnection(
+                    "catalog", Catalog.Type.RELATIONAL, "hive", "comment", 
Collections.emptyMap()));
+    Assertions.assertTrue(exception.getMessage().contains("connection 
failed"));
+  }
 }
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/responses/ErrorConstants.java 
b/common/src/main/java/org/apache/gravitino/dto/responses/ErrorConstants.java
index fcaa2cb90..8772a09d1 100644
--- 
a/common/src/main/java/org/apache/gravitino/dto/responses/ErrorConstants.java
+++ 
b/common/src/main/java/org/apache/gravitino/dto/responses/ErrorConstants.java
@@ -42,6 +42,9 @@ public class ErrorConstants {
   /** Error codes for unsupported operation. */
   public static final int UNSUPPORTED_OPERATION_CODE = 1006;
 
+  /** Error codes for connect to catalog failed. */
+  public static final int CONNECTION_FAILED_CODE = 1007;
+
   /** Error codes for invalid state. */
   public static final int UNKNOWN_ERROR_CODE = 1100;
 
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/responses/ErrorResponse.java 
b/common/src/main/java/org/apache/gravitino/dto/responses/ErrorResponse.java
index f79a9973b..c4706e0ee 100644
--- a/common/src/main/java/org/apache/gravitino/dto/responses/ErrorResponse.java
+++ b/common/src/main/java/org/apache/gravitino/dto/responses/ErrorResponse.java
@@ -27,6 +27,7 @@ import java.util.List;
 import javax.annotation.Nullable;
 import lombok.EqualsAndHashCode;
 import lombok.Getter;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
 import org.apache.gravitino.exceptions.RESTException;
 
 /** Represents an error response. */
@@ -125,6 +126,31 @@ public class ErrorResponse extends BaseResponse {
         getStackTrace(throwable));
   }
 
+  /**
+   * Create a new connection failed error instance of {@link ErrorResponse}.
+   *
+   * @param message The message of the error.
+   * @return The new instance.
+   */
+  public static ErrorResponse connectionFailed(String message) {
+    return connectionFailed(message, null);
+  }
+
+  /**
+   * Create a new connection failed error instance of {@link ErrorResponse}.
+   *
+   * @param message The message of the error.
+   * @param throwable The throwable that caused the error.
+   * @return The new instance.
+   */
+  public static ErrorResponse connectionFailed(String message, Throwable 
throwable) {
+    return new ErrorResponse(
+        ErrorConstants.CONNECTION_FAILED_CODE,
+        ConnectionFailedException.class.getSimpleName(),
+        message,
+        getStackTrace(throwable));
+  }
+
   /**
    * Create a new not found error instance of {@link ErrorResponse}.
    *
diff --git a/core/src/main/java/org/apache/gravitino/StringIdentifier.java 
b/core/src/main/java/org/apache/gravitino/StringIdentifier.java
index 9c7057d21..c60846f6d 100644
--- a/core/src/main/java/org/apache/gravitino/StringIdentifier.java
+++ b/core/src/main/java/org/apache/gravitino/StringIdentifier.java
@@ -37,6 +37,9 @@ public class StringIdentifier {
 
   public static final String ID_KEY = "gravitino.identifier";
 
+  /** For test connection only */
+  public static final StringIdentifier DUMMY_ID = fromId(-1L);
+
   @VisibleForTesting static final int CURRENT_FORMAT_VERSION = 1;
 
   @VisibleForTesting static final String CURRENT_FORMAT = 
"gravitino.v%d.uid%d";
diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java 
b/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
index c2efa83e5..574248f8e 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
@@ -18,6 +18,7 @@
  */
 package org.apache.gravitino.catalog;
 
+import static org.apache.gravitino.StringIdentifier.DUMMY_ID;
 import static org.apache.gravitino.StringIdentifier.ID_KEY;
 import static 
org.apache.gravitino.catalog.PropertiesMetadataHelpers.validatePropertyForAlter;
 import static 
org.apache.gravitino.catalog.PropertiesMetadataHelpers.validatePropertyForCreate;
@@ -67,11 +68,13 @@ import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.StringIdentifier;
 import org.apache.gravitino.connector.BaseCatalog;
+import org.apache.gravitino.connector.CatalogOperations;
 import org.apache.gravitino.connector.HasPropertyMetadata;
 import org.apache.gravitino.connector.PropertyEntry;
 import org.apache.gravitino.connector.SupportsSchemas;
 import org.apache.gravitino.connector.capability.Capability;
 import org.apache.gravitino.exceptions.CatalogAlreadyExistsException;
+import org.apache.gravitino.exceptions.GravitinoRuntimeException;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchEntityException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
@@ -149,6 +152,10 @@ public class CatalogManager implements CatalogDispatcher, 
Closeable {
           });
     }
 
+    public <R> R doWithCatalogOps(ThrowableFunction<CatalogOperations, R> fn) 
throws Exception {
+      return classLoader.withClassLoader(cl -> fn.apply(catalog.ops()));
+    }
+
     public <R> R doWithPartitionOps(
         NameIdentifier tableIdent, ThrowableFunction<SupportsPartitions, R> 
fn) throws Exception {
       return classLoader.withClassLoader(
@@ -333,10 +340,7 @@ public class CatalogManager implements CatalogDispatcher, 
Closeable {
       String comment,
       Map<String, String> properties)
       throws NoSuchMetalakeException, CatalogAlreadyExistsException {
-    // load catalog-related configuration from catalog-specific configuration 
file
-    Map<String, String> newProperties = 
Optional.ofNullable(properties).orElse(Maps.newHashMap());
-    Map<String, String> catalogSpecificConfig = 
loadCatalogSpecificConfig(newProperties, provider);
-    Map<String, String> mergedConfig = mergeConf(newProperties, 
catalogSpecificConfig);
+    Map<String, String> mergedConfig = buildCatalogConf(provider, properties);
 
     long uid = idGenerator.nextId();
     StringIdentifier stringId = StringIdentifier.fromId(uid);
@@ -395,24 +399,69 @@ public class CatalogManager implements CatalogDispatcher, 
Closeable {
     }
   }
 
-  private Pair<Map<String, String>, Map<String, String>> 
getCatalogAlterProperty(
-      CatalogChange... catalogChanges) {
-    Map<String, String> upserts = Maps.newHashMap();
-    Map<String, String> deletes = Maps.newHashMap();
+  /**
+   * Test whether a catalog can be created with the specified parameters, 
without actually creating
+   * it.
+   *
+   * @param ident The identifier of the catalog to be tested.
+   * @param type the type of the catalog.
+   * @param provider the provider of the catalog.
+   * @param comment the comment of the catalog.
+   * @param properties the properties of the catalog.
+   */
+  @Override
+  public void testConnection(
+      NameIdentifier ident,
+      Catalog.Type type,
+      String provider,
+      String comment,
+      Map<String, String> properties) {
+    NameIdentifier metalakeIdent = 
NameIdentifier.of(ident.namespace().levels());
+    try {
+      if (!store.exists(metalakeIdent, EntityType.METALAKE)) {
+        throw new NoSuchMetalakeException(METALAKE_DOES_NOT_EXIST_MSG, 
metalakeIdent);
+      }
 
-    Arrays.stream(catalogChanges)
-        .forEach(
-            catalogChange -> {
-              if (catalogChange instanceof SetProperty) {
-                SetProperty setProperty = (SetProperty) catalogChange;
-                upserts.put(setProperty.getProperty(), setProperty.getValue());
-              } else if (catalogChange instanceof RemoveProperty) {
-                RemoveProperty removeProperty = (RemoveProperty) catalogChange;
-                deletes.put(removeProperty.getProperty(), 
removeProperty.getProperty());
-              }
-            });
+      if (store.exists(ident, EntityType.CATALOG)) {
+        throw new CatalogAlreadyExistsException("Catalog %s already exists", 
ident);
+      }
 
-    return Pair.of(upserts, deletes);
+      Map<String, String> mergedConfig = buildCatalogConf(provider, 
properties);
+      Instant now = Instant.now();
+      String creator = PrincipalUtils.getCurrentPrincipal().getName();
+      CatalogEntity dummyEntity =
+          CatalogEntity.builder()
+              .withId(DUMMY_ID.id())
+              .withName(ident.name())
+              .withNamespace(ident.namespace())
+              .withType(type)
+              .withProvider(provider)
+              .withComment(comment)
+              .withProperties(StringIdentifier.newPropertiesWithId(DUMMY_ID, 
mergedConfig))
+              .withAuditInfo(
+                  AuditInfo.builder()
+                      .withCreator(creator)
+                      .withCreateTime(now)
+                      .withLastModifier(creator)
+                      .withLastModifiedTime(now)
+                      .build())
+              .build();
+
+      CatalogWrapper wrapper = createCatalogWrapper(dummyEntity);
+      wrapper.doWithCatalogOps(
+          c -> {
+            c.testConnection(ident, type, provider, comment, mergedConfig);
+            return null;
+          });
+    } catch (GravitinoRuntimeException e) {
+      throw e;
+    } catch (Exception e) {
+      LOG.warn("Failed to test catalog creation {}", ident, e);
+      if (e instanceof RuntimeException) {
+        throw (RuntimeException) e;
+      }
+      throw new RuntimeException(e);
+    }
   }
 
   /**
@@ -550,6 +599,33 @@ public class CatalogManager implements CatalogDispatcher, 
Closeable {
     return catalogCache.get(ident, this::loadCatalogInternal);
   }
 
+  private Map<String, String> buildCatalogConf(String provider, Map<String, 
String> properties) {
+    Map<String, String> newProperties = 
Optional.ofNullable(properties).orElse(Maps.newHashMap());
+    // load catalog-related configuration from catalog-specific configuration 
file
+    Map<String, String> catalogSpecificConfig = 
loadCatalogSpecificConfig(newProperties, provider);
+    return mergeConf(newProperties, catalogSpecificConfig);
+  }
+
+  private Pair<Map<String, String>, Map<String, String>> 
getCatalogAlterProperty(
+      CatalogChange... catalogChanges) {
+    Map<String, String> upserts = Maps.newHashMap();
+    Map<String, String> deletes = Maps.newHashMap();
+
+    Arrays.stream(catalogChanges)
+        .forEach(
+            catalogChange -> {
+              if (catalogChange instanceof SetProperty) {
+                SetProperty setProperty = (SetProperty) catalogChange;
+                upserts.put(setProperty.getProperty(), setProperty.getValue());
+              } else if (catalogChange instanceof RemoveProperty) {
+                RemoveProperty removeProperty = (RemoveProperty) catalogChange;
+                deletes.put(removeProperty.getProperty(), 
removeProperty.getProperty());
+              }
+            });
+
+    return Pair.of(upserts, deletes);
+  }
+
   private void checkMetalakeExists(NameIdentifier ident) throws 
NoSuchMetalakeException {
     try {
       if (!store.exists(ident, EntityType.METALAKE)) {
diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/CatalogNormalizeDispatcher.java
 
b/core/src/main/java/org/apache/gravitino/catalog/CatalogNormalizeDispatcher.java
index 725527843..32923acb8 100644
--- 
a/core/src/main/java/org/apache/gravitino/catalog/CatalogNormalizeDispatcher.java
+++ 
b/core/src/main/java/org/apache/gravitino/catalog/CatalogNormalizeDispatcher.java
@@ -104,6 +104,18 @@ public class CatalogNormalizeDispatcher implements 
CatalogDispatcher {
     return dispatcher.dropCatalog(ident);
   }
 
+  @Override
+  public void testConnection(
+      NameIdentifier ident,
+      Catalog.Type type,
+      String provider,
+      String comment,
+      Map<String, String> properties)
+      throws Exception {
+    validateCatalogName(ident.name());
+    dispatcher.testConnection(ident, type, provider, comment, properties);
+  }
+
   private void validateCatalogName(String name) throws 
IllegalArgumentException {
     if (RESERVED_WORDS.contains(name.toLowerCase())) {
       throw new IllegalArgumentException("The catalog name '" + name + "' is 
reserved.");
diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/SupportsCatalogs.java 
b/core/src/main/java/org/apache/gravitino/catalog/SupportsCatalogs.java
index ddec0581f..33f5d4094 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/SupportsCatalogs.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/SupportsCatalogs.java
@@ -121,4 +121,22 @@ public interface SupportsCatalogs {
    * @return True if the catalog was dropped, false if the catalog does not 
exist.
    */
   boolean dropCatalog(NameIdentifier ident);
+
+  /**
+   * Test whether the catalog with specified parameters can be connected to 
before creating it.
+   *
+   * @param ident The identifier of the catalog to be tested.
+   * @param type the type of the catalog.
+   * @param provider the provider of the catalog.
+   * @param comment the comment of the catalog.
+   * @param properties the properties of the catalog.
+   * @throws Exception If the connection test fails.
+   */
+  void testConnection(
+      NameIdentifier ident,
+      Catalog.Type type,
+      String provider,
+      String comment,
+      Map<String, String> properties)
+      throws Exception;
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/connector/CatalogOperations.java 
b/core/src/main/java/org/apache/gravitino/connector/CatalogOperations.java
index 14906d005..3d7f944d7 100644
--- a/core/src/main/java/org/apache/gravitino/connector/CatalogOperations.java
+++ b/core/src/main/java/org/apache/gravitino/connector/CatalogOperations.java
@@ -20,6 +20,8 @@ package org.apache.gravitino.connector;
 
 import java.io.Closeable;
 import java.util.Map;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.SupportsSchemas;
 import org.apache.gravitino.annotation.Evolving;
 import org.apache.gravitino.rel.TableCatalog;
@@ -46,4 +48,22 @@ public interface CatalogOperations extends Closeable {
   void initialize(
       Map<String, String> config, CatalogInfo info, HasPropertyMetadata 
propertiesMetadata)
       throws RuntimeException;
+
+  /**
+   * Test whether a catalog can be created with the specified parameters, 
without actually creating
+   * it.
+   *
+   * @param catalogIdent the name of the catalog.
+   * @param type the type of the catalog.
+   * @param provider the provider of the catalog.
+   * @param comment the comment of the catalog.
+   * @param properties the properties of the catalog.
+   */
+  void testConnection(
+      NameIdentifier catalogIdent,
+      Catalog.Type type,
+      String provider,
+      String comment,
+      Map<String, String> properties)
+      throws Exception;
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/CatalogEventDispatcher.java 
b/core/src/main/java/org/apache/gravitino/listener/CatalogEventDispatcher.java
index e4072d022..93cffe351 100644
--- 
a/core/src/main/java/org/apache/gravitino/listener/CatalogEventDispatcher.java
+++ 
b/core/src/main/java/org/apache/gravitino/listener/CatalogEventDispatcher.java
@@ -156,4 +156,16 @@ public class CatalogEventDispatcher implements 
CatalogDispatcher {
       throw e;
     }
   }
+
+  @Override
+  public void testConnection(
+      NameIdentifier ident,
+      Catalog.Type type,
+      String provider,
+      String comment,
+      Map<String, String> properties)
+      throws Exception {
+    // TODO: Support event dispatching for testConnection
+    dispatcher.testConnection(ident, type, provider, comment, properties);
+  }
 }
diff --git 
a/core/src/test/java/org/apache/gravitino/catalog/DummyCatalogOperations.java 
b/core/src/test/java/org/apache/gravitino/catalog/DummyCatalogOperations.java
index 70f5e7f1a..7ad6ba787 100644
--- 
a/core/src/test/java/org/apache/gravitino/catalog/DummyCatalogOperations.java
+++ 
b/core/src/test/java/org/apache/gravitino/catalog/DummyCatalogOperations.java
@@ -20,6 +20,8 @@ package org.apache.gravitino.catalog;
 
 import java.io.IOException;
 import java.util.Map;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.connector.CatalogInfo;
 import org.apache.gravitino.connector.CatalogOperations;
 import org.apache.gravitino.connector.HasPropertyMetadata;
@@ -31,6 +33,15 @@ public class DummyCatalogOperations implements 
CatalogOperations {
       Map<String, String> config, CatalogInfo info, HasPropertyMetadata 
propertiesMetadata)
       throws RuntimeException {}
 
+  @Override
+  public void testConnection(
+      NameIdentifier catalogIdent,
+      Catalog.Type type,
+      String provider,
+      String comment,
+      Map<String, String> properties)
+      throws Exception {}
+
   @Override
   public void close() throws IOException {}
 }
diff --git 
a/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java 
b/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java
index d1e9c623e..83d7308ca 100644
--- a/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java
+++ b/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java
@@ -234,6 +234,12 @@ public class TestCatalogManager {
     props.put("key1", "value1");
     props.put("key2", "value2");
 
+    // test before creation
+    Assertions.assertDoesNotThrow(
+        () ->
+            catalogManager.testConnection(
+                ident, Catalog.Type.RELATIONAL, provider, "comment", props));
+
     Catalog testCatalog =
         catalogManager.createCatalog(ident, Catalog.Type.RELATIONAL, provider, 
"comment", props);
     Assertions.assertEquals("test1", testCatalog.name());
@@ -243,8 +249,15 @@ public class TestCatalogManager {
 
     Assertions.assertNotNull(catalogManager.catalogCache.getIfPresent(ident));
 
-    // Test create under non-existed metalake
+    // test before creation
     NameIdentifier ident2 = NameIdentifier.of("metalake1", "test1");
+    Assertions.assertThrows(
+        NoSuchMetalakeException.class,
+        () ->
+            catalogManager.testConnection(
+                ident2, Catalog.Type.RELATIONAL, provider, "comment", props));
+
+    // Test create under non-existed metalake
     Throwable exception1 =
         Assertions.assertThrows(
             NoSuchMetalakeException.class,
@@ -254,6 +267,13 @@ public class TestCatalogManager {
     Assertions.assertTrue(exception1.getMessage().contains("Metalake metalake1 
does not exist"));
     Assertions.assertNull(catalogManager.catalogCache.getIfPresent(ident2));
 
+    // test before creation
+    Assertions.assertThrows(
+        NoSuchMetalakeException.class,
+        () ->
+            catalogManager.testConnection(
+                ident2, Catalog.Type.RELATIONAL, provider, "comment", props));
+
     // Test create with duplicated name
     Throwable exception2 =
         Assertions.assertThrows(
diff --git 
a/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java 
b/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java
index ba609edca..7588af17c 100644
--- 
a/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java
+++ 
b/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.time.Instant;
 import java.util.HashMap;
 import java.util.Map;
+import org.apache.gravitino.Catalog;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.Schema;
@@ -31,6 +32,7 @@ import org.apache.gravitino.TestFileset;
 import org.apache.gravitino.TestSchema;
 import org.apache.gravitino.TestTable;
 import org.apache.gravitino.TestTopic;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
 import org.apache.gravitino.exceptions.FilesetAlreadyExistsException;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchFilesetException;
@@ -71,6 +73,8 @@ public class TestCatalogOperations
 
   public static final String FAIL_CREATE = "fail-create";
 
+  public static final String FAIL_TEST = "need-fail";
+
   public TestCatalogOperations(Map<String, String> config) {
     tables = Maps.newHashMap();
     schemas = Maps.newHashMap();
@@ -520,4 +524,16 @@ public class TestCatalogOperations
       return false;
     }
   }
+
+  @Override
+  public void testConnection(
+      NameIdentifier name,
+      Catalog.Type type,
+      String provider,
+      String comment,
+      Map<String, String> properties) {
+    if ("true".equals(properties.get(FAIL_TEST))) {
+      throw new ConnectionFailedException("Connection failed");
+    }
+  }
 }
diff --git a/docs/open-api/catalogs.yaml b/docs/open-api/catalogs.yaml
index de89b253a..ab68c06ed 100644
--- a/docs/open-api/catalogs.yaml
+++ b/docs/open-api/catalogs.yaml
@@ -79,6 +79,65 @@ paths:
         "5xx":
           $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
 
+  /metalakes/{metalake}/catalogs/testConnection:
+    parameters:
+      - $ref: "./openapi.yaml#/components/parameters/metalake"
+    post:
+      tags:
+        - catalog
+      summary: Test catalog connection
+      operationId: testConnection
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/CatalogCreateRequest"
+            examples:
+              CatalogCreate:
+                $ref: "#/components/examples/CatalogCreate"
+      responses:
+        "200":
+          description: Test connection completed
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                type: object
+                required:
+                  - code
+                properties:
+                  code:
+                      type: integer
+                      format: int32
+                      description: Status code of the response
+                  type:
+                    type: string
+                    description: Internal type definition of the exception
+                  message:
+                    type: string
+                    description: The message of the exception
+                  stack:
+                    type: array
+                    items:
+                      type: string
+                      description: The stack trace of the exception
+              examples:
+                TestConnectionSuccess:
+                  value: {
+                    "code": 0
+                  }
+                TestConnectionFailed:
+                  $ref: "#/components/examples/ConnectionFailedException"
+                CatalogAlreadyExists:
+                  $ref: "#/components/examples/CatalogAlreadyExistsException"
+                MetalakeNotFound:
+                  $ref: 
"./metalakes.yaml#/components/examples/NoSuchMetalakeException"
+        "400":
+          $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+
+
   /metalakes/{metalake}/catalogs/{catalog}:
     parameters:
       - $ref: "./openapi.yaml#/components/parameters/metalake"
@@ -495,6 +554,17 @@ components:
         ]
       }
 
+    ConnectionFailedException:
+      value: {
+        "code": 1007,
+        "type": "ConnectionFailedException",
+        "message": "Failed to run getAllDatabases in Hive Metastore: Failed to 
connect to Hive Metastore",
+        "stack": [
+          "org.apache.gravitino.exceptions.ConnectionFailedException: Failed 
to run getAllDatabases in Hive Metastore: Failed to connect to Hive Metastore",
+          "..."
+        ]
+      }
+
     NoSuchCatalogException:
       value: {
         "code": 1003,
diff --git a/docs/open-api/openapi.yaml b/docs/open-api/openapi.yaml
index afc011446..9218a6b67 100644
--- a/docs/open-api/openapi.yaml
+++ b/docs/open-api/openapi.yaml
@@ -61,6 +61,9 @@ paths:
   /metalakes/{metalake}/catalogs:
     $ref: "./catalogs.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1catalogs"
 
+  /metalakes/{metalake}/catalogs/testConnection:
+    $ref: 
"./catalogs.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1catalogs~1testConnection"
+
   /metalakes/{metalake}/catalogs/{catalog}:
     $ref: 
"./catalogs.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1catalogs~1%7Bcatalog%7D"
 
diff --git 
a/integration-test/src/test/java/org/apache/gravitino/integration/test/client/CatalogIT.java
 
b/integration-test/src/test/java/org/apache/gravitino/integration/test/client/CatalogIT.java
index 943f8e17e..60f7a7dd0 100644
--- 
a/integration-test/src/test/java/org/apache/gravitino/integration/test/client/CatalogIT.java
+++ 
b/integration-test/src/test/java/org/apache/gravitino/integration/test/client/CatalogIT.java
@@ -84,12 +84,19 @@ public class CatalogIT extends AbstractIT {
   }
 
   @Test
-  public void testCreateCatalog() {
+  public void testTestConnection() {
     String catalogName = GravitinoITUtils.genRandomName("catalog");
     Assertions.assertFalse(metalake.catalogExists(catalogName));
 
     Map<String, String> properties = Maps.newHashMap();
     properties.put("metastore.uris", hmsUri);
+    // test before creation
+    Assertions.assertDoesNotThrow(
+        () ->
+            metalake.testConnection(
+                catalogName, Catalog.Type.RELATIONAL, "hive", "catalog 
comment", properties));
+
+    // test creation
     Catalog catalog =
         metalake.createCatalog(
             catalogName, Catalog.Type.RELATIONAL, "hive", "catalog comment", 
properties);
@@ -140,7 +147,16 @@ public class CatalogIT extends AbstractIT {
 
     // test cloud related properties
     ImmutableMap<String, String> illegalProps = ImmutableMap.of("cloud.name", 
"myCloud");
-    IllegalArgumentException exception =
+    // test before creation
+    Exception exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                metalake.testConnection(
+                    catalogName, Catalog.Type.FILESET, "hadoop", "catalog 
comment", illegalProps));
+    Assertions.assertTrue(exception.getMessage().contains("Invalid value 
[myCloud]"));
+
+    exception =
         Assertions.assertThrows(
             IllegalArgumentException.class,
             () ->
diff --git 
a/integration-test/src/test/java/org/apache/gravitino/integration/test/client/MetalakeIT.java
 
b/integration-test/src/test/java/org/apache/gravitino/integration/test/client/MetalakeIT.java
index c95f0e201..3d87e9d4c 100644
--- 
a/integration-test/src/test/java/org/apache/gravitino/integration/test/client/MetalakeIT.java
+++ 
b/integration-test/src/test/java/org/apache/gravitino/integration/test/client/MetalakeIT.java
@@ -30,6 +30,7 @@ import org.apache.gravitino.MetalakeChange;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.auth.AuthConstants;
 import org.apache.gravitino.client.GravitinoMetalake;
+import org.apache.gravitino.exceptions.IllegalNameIdentifierException;
 import org.apache.gravitino.exceptions.MetalakeAlreadyExistsException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.integration.test.util.AbstractIT;
@@ -98,7 +99,7 @@ public class MetalakeIT extends AbstractIT {
         });
 
     // metalake empty name - note it's NameIdentifier.of("") that fails not 
the load
-    assertThrows(IllegalArgumentException.class, () -> 
client.loadMetalake(""));
+    assertThrows(IllegalNameIdentifierException.class, () -> 
client.loadMetalake(""));
   }
 
   @Test
diff --git a/server/src/main/java/org/apache/gravitino/server/web/Utils.java 
b/server/src/main/java/org/apache/gravitino/server/web/Utils.java
index d092bd0a9..e2405a6ad 100644
--- a/server/src/main/java/org/apache/gravitino/server/web/Utils.java
+++ b/server/src/main/java/org/apache/gravitino/server/web/Utils.java
@@ -57,6 +57,20 @@ public class Utils {
         .build();
   }
 
+  public static Response connectionFailed(String message) {
+    return Response.status(Response.Status.BAD_GATEWAY)
+        .entity(ErrorResponse.connectionFailed(message))
+        .type(MediaType.APPLICATION_JSON)
+        .build();
+  }
+
+  public static Response connectionFailed(String message, Throwable throwable) 
{
+    return Response.status(Response.Status.BAD_GATEWAY)
+        .entity(ErrorResponse.connectionFailed(message, throwable))
+        .type(MediaType.APPLICATION_JSON)
+        .build();
+  }
+
   public static Response internalError(String message) {
     return internalError(message, null);
   }
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java
index 78a1c540c..ebb1813e2 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java
@@ -43,6 +43,7 @@ import org.apache.gravitino.catalog.CatalogDispatcher;
 import org.apache.gravitino.dto.requests.CatalogCreateRequest;
 import org.apache.gravitino.dto.requests.CatalogUpdateRequest;
 import org.apache.gravitino.dto.requests.CatalogUpdatesRequest;
+import org.apache.gravitino.dto.responses.BaseResponse;
 import org.apache.gravitino.dto.responses.CatalogListResponse;
 import org.apache.gravitino.dto.responses.CatalogResponse;
 import org.apache.gravitino.dto.responses.DropResponse;
@@ -148,6 +149,44 @@ public class CatalogOperations {
     }
   }
 
+  @POST
+  @Path("testConnection")
+  @Produces("application/vnd.gravitino.v1+json")
+  @Timed(name = "test-connection." + MetricNames.HTTP_PROCESS_DURATION, 
absolute = true)
+  @ResponseMetered(name = "test-connection", absolute = true)
+  public Response testConnection(
+      @PathParam("metalake") String metalake, CatalogCreateRequest request) {
+    LOG.info("Received test connection request for catalog: {}.{}", metalake, 
request.getName());
+    try {
+      return Utils.doAs(
+          httpRequest,
+          () -> {
+            request.validate();
+            NameIdentifier ident = NameIdentifierUtil.ofCatalog(metalake, 
request.getName());
+            TreeLockUtils.doWithTreeLock(
+                NameIdentifierUtil.ofMetalake(metalake),
+                LockType.READ,
+                () -> {
+                  catalogDispatcher.testConnection(
+                      ident,
+                      request.getType(),
+                      request.getProvider(),
+                      request.getComment(),
+                      request.getProperties());
+                  return null;
+                });
+            Response response = Utils.ok(new BaseResponse());
+            LOG.info(
+                "Successfully test connection for catalog: {}.{}", metalake, 
request.getName());
+            return response;
+          });
+
+    } catch (Exception e) {
+      LOG.info("Failed to test connection for catalog: {}.{}", metalake, 
request.getName());
+      return ExceptionHandlers.handleTestConnectionException(e);
+    }
+  }
+
   @GET
   @Path("{catalog}")
   @Produces("application/vnd.gravitino.v1+json")
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
index cd57c570a..677c42de9 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
@@ -19,8 +19,12 @@
 package org.apache.gravitino.server.web.rest;
 
 import com.google.common.annotations.VisibleForTesting;
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.exceptions.AlreadyExistsException;
 import org.apache.gravitino.exceptions.CatalogAlreadyExistsException;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
 import org.apache.gravitino.exceptions.FilesetAlreadyExistsException;
 import org.apache.gravitino.exceptions.GroupAlreadyExistsException;
 import org.apache.gravitino.exceptions.MetalakeAlreadyExistsException;
@@ -103,6 +107,30 @@ public class ExceptionHandlers {
     return GroupPermissionOperationExceptionHandler.INSTANCE.handle(op, roles, 
parent, e);
   }
 
+  public static Response handleTestConnectionException(Exception e) {
+    ErrorResponse response;
+    if (e instanceof IllegalArgumentException) {
+      response = ErrorResponse.illegalArguments(e.getMessage(), e);
+
+    } else if (e instanceof ConnectionFailedException) {
+      response = ErrorResponse.connectionFailed(e.getMessage(), e);
+
+    } else if (e instanceof NotFoundException) {
+      response = ErrorResponse.notFound(e.getClass().getSimpleName(), 
e.getMessage(), e);
+
+    } else if (e instanceof AlreadyExistsException) {
+      response = ErrorResponse.alreadyExists(e.getClass().getSimpleName(), 
e.getMessage(), e);
+
+    } else {
+      return Utils.internalError(e.getMessage(), e);
+    }
+
+    return Response.status(Response.Status.OK)
+        .entity(response)
+        .type(MediaType.APPLICATION_JSON)
+        .build();
+  }
+
   private static class PartitionExceptionHandler extends BaseExceptionHandler {
 
     private static final ExceptionHandler INSTANCE = new 
PartitionExceptionHandler();
@@ -231,6 +259,9 @@ public class ExceptionHandlers {
       if (e instanceof IllegalArgumentException) {
         return Utils.illegalArguments(errorMsg, e);
 
+      } else if (e instanceof ConnectionFailedException) {
+        return Utils.connectionFailed(errorMsg, e);
+
       } else if (e instanceof NotFoundException) {
         return Utils.notFound(errorMsg, e);
 
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/TestUtils.java 
b/server/src/test/java/org/apache/gravitino/server/web/TestUtils.java
index 950a2905e..adaaacaed 100644
--- a/server/src/test/java/org/apache/gravitino/server/web/TestUtils.java
+++ b/server/src/test/java/org/apache/gravitino/server/web/TestUtils.java
@@ -92,6 +92,16 @@ public class TestUtils {
     assertEquals("Invalid argument", errorResponse.getMessage());
   }
 
+  @Test
+  public void testConnectionFailed() {
+    Response response = Utils.connectionFailed("Connection failed");
+    assertNotNull(response);
+    assertEquals(Response.Status.BAD_GATEWAY.getStatusCode(), 
response.getStatus());
+    assertEquals(MediaType.APPLICATION_JSON, 
response.getMediaType().toString());
+    ErrorResponse errorResponse = (ErrorResponse) response.getEntity();
+    assertEquals("Connection failed", errorResponse.getMessage());
+  }
+
   @Test
   public void testInternalError() {
     Response response = Utils.internalError("Internal error");
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalog.java 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalog.java
index 73297a4a5..bcd5107b4 100644
--- a/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalog.java
+++ b/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalog.java
@@ -21,6 +21,7 @@ package org.apache.gravitino.server.web.rest;
 import com.google.common.collect.ImmutableMap;
 import java.io.IOException;
 import java.util.Map;
+import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.connector.BaseCatalog;
 import org.apache.gravitino.connector.CatalogInfo;
 import org.apache.gravitino.connector.CatalogOperations;
@@ -44,6 +45,15 @@ public class TestCatalog extends BaseCatalog<TestCatalog> {
           Map<String, String> config, CatalogInfo info, HasPropertyMetadata 
propertiesMetadata)
           throws RuntimeException {}
 
+      @Override
+      public void testConnection(
+          NameIdentifier catalogIdent,
+          Type type,
+          String provider,
+          String comment,
+          Map<String, String> properties)
+          throws Exception {}
+
       @Override
       public void close() throws IOException {}
     };
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java
index da011ba34..98ac3ed40 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java
@@ -18,10 +18,12 @@
  */
 package org.apache.gravitino.server.web.rest;
 
+import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
 import static org.apache.gravitino.Configs.TREE_LOCK_CLEAN_INTERVAL;
 import static org.apache.gravitino.Configs.TREE_LOCK_MAX_NODE_IN_MEMORY;
 import static org.apache.gravitino.Configs.TREE_LOCK_MIN_NODE_IN_MEMORY;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -48,6 +50,7 @@ import org.apache.gravitino.dto.CatalogDTO;
 import org.apache.gravitino.dto.requests.CatalogCreateRequest;
 import org.apache.gravitino.dto.requests.CatalogUpdateRequest;
 import org.apache.gravitino.dto.requests.CatalogUpdatesRequest;
+import org.apache.gravitino.dto.responses.BaseResponse;
 import org.apache.gravitino.dto.responses.CatalogListResponse;
 import org.apache.gravitino.dto.responses.CatalogResponse;
 import org.apache.gravitino.dto.responses.DropResponse;
@@ -280,14 +283,53 @@ public class TestCatalogOperations extends JerseyTest {
             .accept("application/vnd.gravitino.v1+json")
             .post(Entity.entity(req, MediaType.APPLICATION_JSON_TYPE));
 
-    Assertions.assertEquals(
-        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp3.getStatus());
+    Assertions.assertEquals(INTERNAL_SERVER_ERROR.getStatusCode(), 
resp3.getStatus());
 
     ErrorResponse errorResponse2 = resp3.readEntity(ErrorResponse.class);
     Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, 
errorResponse2.getCode());
     Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResponse2.getType());
   }
 
+  @Test
+  public void testConnection() {
+    CatalogCreateRequest req =
+        new CatalogCreateRequest(
+            "catalog1",
+            Catalog.Type.RELATIONAL,
+            "test",
+            "comment",
+            ImmutableMap.of("key", "value"));
+    doNothing().when(manager).testConnection(any(), any(), any(), any(), 
any());
+    Response resp =
+        target("/metalakes/metalake1/catalogs/testConnection")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .post(Entity.entity(req, MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    BaseResponse testResponse = resp.readEntity(BaseResponse.class);
+    Assertions.assertEquals(0, testResponse.getCode());
+
+    // test throw RuntimeException
+    doThrow(new RuntimeException("connection failed"))
+        .when(manager)
+        .testConnection(any(), any(), any(), any(), any());
+    Response resp1 =
+        target("/metalakes/metalake1/catalogs/testConnection")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .post(Entity.entity(req, MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(INTERNAL_SERVER_ERROR.getStatusCode(), 
resp1.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp1.getMediaType());
+
+    ErrorResponse errorResponse = resp1.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, 
errorResponse.getCode());
+    Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResponse.getType());
+  }
+
   @Test
   public void testLoadCatalog() {
     TestCatalog catalog = buildCatalog("metalake1", "catalog1");
@@ -347,8 +389,7 @@ public class TestCatalogOperations extends JerseyTest {
             .accept("application/vnd.gravitino.v1+json")
             .get();
 
-    Assertions.assertEquals(
-        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp3.getStatus());
+    Assertions.assertEquals(INTERNAL_SERVER_ERROR.getStatusCode(), 
resp3.getStatus());
 
     ErrorResponse errorResponse2 = resp3.readEntity(ErrorResponse.class);
     Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, 
errorResponse2.getCode());
@@ -418,8 +459,7 @@ public class TestCatalogOperations extends JerseyTest {
             .accept("application/vnd.gravitino.v1+json")
             .put(Entity.entity(req, MediaType.APPLICATION_JSON_TYPE));
 
-    Assertions.assertEquals(
-        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp4.getStatus());
+    Assertions.assertEquals(INTERNAL_SERVER_ERROR.getStatusCode(), 
resp4.getStatus());
 
     ErrorResponse errorResponse2 = resp4.readEntity(ErrorResponse.class);
     Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, 
errorResponse2.getCode());
@@ -463,8 +503,7 @@ public class TestCatalogOperations extends JerseyTest {
             .accept("application/vnd.gravitino.v1+json")
             .delete();
 
-    Assertions.assertEquals(
-        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp3.getStatus());
+    Assertions.assertEquals(INTERNAL_SERVER_ERROR.getStatusCode(), 
resp3.getStatus());
 
     ErrorResponse errorResponse = resp3.readEntity(ErrorResponse.class);
     Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, 
errorResponse.getCode());


Reply via email to