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

jerryshao pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git

commit 23189ae91af1a1c93965242710ea07a9b076d8f3
Author: MaSai <[email protected]>
AuthorDate: Wed Sep 9 19:28:31 2026 +0800

    [#12998] fix(catalogs): Preserve upstream error messages when wrapping 
exceptions (#12999)
    
    - Add `ExceptionMessages` helper to append the underlying cause message
    when wrapping exceptions.
    - Update catalog catch-all wraps (Kafka, Glue, Hive/HMS, Fileset, Model,
    Lance, Doris, Paimon, Hadoop FS) to use it.
    - Kafka: map invalid configuration on create/alter to
    `IllegalArgumentException` (4xx).
    - Glue: keep upstream text in default errors; map
    `AccessDeniedException` to `ForbiddenException`.
    - Add unit tests for `ExceptionMessages` and `GlueExceptionConverter`.
    
    Connectors were replacing actionable upstream errors with generic
    messages. Operators only saw the stack trace. Client-caused failures
    were also returned as 500.
    
    Fix: #12998
    
    Yes. Failed catalog operations may return richer `message` text
    (upstream reason included). Some Kafka invalid-config and Glue
    access-denied failures map to 400/403 instead of 500.
    
    - Unit tests: `TestExceptionMessages`, `TestGlueExceptionConverter`
    - Suggested local run:
    `./gradlew spotlessApply :common:test --tests
    org.apache.gravitino.utils.TestExceptionMessages
    :catalogs:catalog-glue:test --tests
    org.apache.gravitino.catalog.glue.TestGlueExceptionConverter -PskipITs`
    
    Made with [Cursor](https://cursor.com)
    
    ---------
    
    Co-authored-by: Cursor <[email protected]>
---
 .../catalog/fileset/FilesetCatalogOperations.java  |  38 ++---
 .../catalog/glue/GlueExceptionConverter.java       |  13 +-
 .../catalog/glue/GlueTableOperations.java          |  11 +-
 .../catalog/glue/TestGlueExceptionConverter.java   |   5 +-
 .../catalog/hive/HiveCatalogOperations.java        |   3 +-
 .../catalog/hive/HiveTableOperations.java          |   9 +-
 .../catalog/hive/HiveViewCatalogOperations.java    |  19 +--
 .../doris/operation/DorisTableOperations.java      |   3 +-
 .../catalog/kafka/KafkaCatalogOperations.java      |  70 ++++++----
 .../generic/GenericCatalogOperations.java          |   7 +-
 .../lakehouse/lance/LanceTableOperations.java      |  24 ++--
 .../lakehouse/paimon/utils/CatalogUtils.java       |   3 +-
 .../catalog/model/ModelCatalogOperations.java      |  29 ++--
 .../catalog/hadoop/fs/FileSystemUtils.java         |   7 +-
 .../catalog/hadoop/fs/HDFSFileSystemProxy.java     |   5 +-
 .../gravitino/hive/client/HiveClientFactory.java   |   5 +-
 .../gravitino/hive/client/HiveClientImpl.java      |   5 +-
 .../hive/client/HiveExceptionConverter.java        |   6 +-
 .../gravitino/hive/client/ProxyHiveClientImpl.java |   3 +-
 .../org/apache/gravitino/hive/client/Util.java     |   3 +-
 .../gravitino/hive/kerberos/KerberosClient.java    |   3 +-
 .../apache/gravitino/utils/ExceptionMessages.java  | 155 +++++++++++++++++++++
 .../gravitino/utils/TestExceptionMessages.java     | 143 +++++++++++++++++++
 23 files changed, 457 insertions(+), 112 deletions(-)

diff --git 
a/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogOperations.java
 
b/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogOperations.java
index 7753ff6d42..ac75ec03c2 100644
--- 
a/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogOperations.java
+++ 
b/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogOperations.java
@@ -39,6 +39,7 @@ import com.google.common.collect.Maps;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -116,6 +117,7 @@ import org.apache.gravitino.meta.SchemaEntity;
 import org.apache.gravitino.metrics.MetricsSystem;
 import org.apache.gravitino.metrics.source.FilesetCatalogMetricsSource;
 import org.apache.gravitino.utils.ClassLoaderResourceCleanerUtils;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.apache.gravitino.utils.FilesetUtil;
 import org.apache.gravitino.utils.NameIdentifierUtil;
 import org.apache.gravitino.utils.NamespaceUtil;
@@ -347,7 +349,7 @@ public class FilesetCatalogOperations extends 
ManagedSchemaOperations
           .map(f -> NameIdentifier.of(namespace, f.name()))
           .toArray(NameIdentifier[]::new);
     } catch (IOException e) {
-      throw new RuntimeException("Failed to list filesets under namespace " + 
namespace, e);
+      throw ExceptionMessages.wrap("Failed to list filesets under namespace " 
+ namespace, e);
     }
   }
 
@@ -369,7 +371,7 @@ public class FilesetCatalogOperations extends 
ManagedSchemaOperations
     } catch (NoSuchEntityException exception) {
       throw new NoSuchFilesetException(exception, FILESET_DOES_NOT_EXIST_MSG, 
ident);
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to load fileset %s" + ident, ioe);
+      throw ExceptionMessages.wrap("Failed to load fileset %s" + ident, ioe);
     }
   }
 
@@ -416,7 +418,7 @@ public class FilesetCatalogOperations extends 
ManagedSchemaOperations
           .toArray(FileInfo[]::new);
 
     } catch (IOException e) {
-      throw new RuntimeException("Failed to list files in fileset" + 
filesetIdent, e);
+      throw ExceptionMessages.wrap("Failed to list files in fileset" + 
filesetIdent, e);
     }
   }
 
@@ -444,7 +446,7 @@ public class FilesetCatalogOperations extends 
ManagedSchemaOperations
         throw new FilesetAlreadyExistsException("Fileset %s already exists", 
ident);
       }
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to check if fileset " + ident + " 
exists", ioe);
+      throw ExceptionMessages.wrap("Failed to check if fileset " + ident + " 
exists", ioe);
     }
 
     SchemaEntity schemaEntity;
@@ -454,7 +456,7 @@ public class FilesetCatalogOperations extends 
ManagedSchemaOperations
     } catch (NoSuchEntityException exception) {
       throw new NoSuchSchemaException(exception, SCHEMA_DOES_NOT_EXIST_MSG, 
schemaIdent);
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to load schema " + schemaIdent, ioe);
+      throw ExceptionMessages.wrap("Failed to load schema " + schemaIdent, 
ioe);
     }
 
     // For external fileset, the storageLocation must be set.
@@ -557,7 +559,7 @@ public class FilesetCatalogOperations extends 
ManagedSchemaOperations
         }
 
       } catch (IOException ioe) {
-        throw new RuntimeException("Failed to create fileset " + ident, ioe);
+        throw ExceptionMessages.wrap("Failed to create fileset " + ident, ioe);
       }
     }
 
@@ -591,7 +593,7 @@ public class FilesetCatalogOperations extends 
ManagedSchemaOperations
     try {
       store.put(filesetEntity, true /* overwrite */);
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to create fileset " + ident, ioe);
+      throw ExceptionMessages.wrap("Failed to create fileset " + ident, ioe);
     }
 
     return FilesetImpl.builder()
@@ -654,7 +656,7 @@ public class FilesetCatalogOperations extends 
ManagedSchemaOperations
         throw new NoSuchFilesetException(FILESET_DOES_NOT_EXIST_MSG, ident);
       }
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to load fileset " + ident, ioe);
+      throw ExceptionMessages.wrap("Failed to load fileset " + ident, ioe);
     }
 
     try {
@@ -674,12 +676,12 @@ public class FilesetCatalogOperations extends 
ManagedSchemaOperations
           .withAuditInfo(updatedFilesetEntity.auditInfo())
           .build();
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to update fileset " + ident, ioe);
+      throw ExceptionMessages.wrap("Failed to update fileset " + ident, ioe);
     } catch (NoSuchEntityException nsee) {
       throw new NoSuchFilesetException(nsee, FILESET_DOES_NOT_EXIST_MSG, 
ident);
     } catch (AlreadyExistsException aee) {
       // This is happened when renaming a fileset to an existing fileset name.
-      throw new RuntimeException(
+      throw ExceptionMessages.wrap(
           "Fileset with the same name " + ident.name() + " already exists", 
aee);
     }
   }
@@ -735,8 +737,10 @@ public class FilesetCatalogOperations extends 
ManagedSchemaOperations
     } catch (NoSuchEntityException ne) {
       LOG.warn("Fileset {} does not exist", ident);
       return false;
+    } catch (UncheckedIOException uioe) {
+      throw ExceptionMessages.wrap("Failed to delete fileset " + ident, 
uioe.getCause());
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to delete fileset " + ident, ioe);
+      throw ExceptionMessages.wrap("Failed to delete fileset " + ident, ioe);
     }
   }
 
@@ -760,7 +764,7 @@ public class FilesetCatalogOperations extends 
ManagedSchemaOperations
         throw new SchemaAlreadyExistsException("Schema %s already exists", 
ident);
       }
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to check if schema " + ident + " 
exists", ioe);
+      throw ExceptionMessages.wrap("Failed to check if schema " + ident + " 
exists", ioe);
     }
 
     Map<String, Path> schemaPaths = getAndCheckSchemaPaths(ident.name(), 
properties);
@@ -804,7 +808,7 @@ public class FilesetCatalogOperations extends 
ManagedSchemaOperations
               }
 
             } catch (IOException ioe) {
-              throw new RuntimeException(
+              throw ExceptionMessages.wrap(
                   "Failed to create schema " + ident + " location " + 
schemaPath, ioe);
             }
           }
@@ -821,7 +825,7 @@ public class FilesetCatalogOperations extends 
ManagedSchemaOperations
         throw new NoSuchSchemaException(SCHEMA_DOES_NOT_EXIST_MSG, ident);
       }
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to check if schema " + ident + " 
exists", ioe);
+      throw ExceptionMessages.wrap("Failed to check if schema " + ident + " 
exists", ioe);
     }
 
     // note: we need to invalidate the related fileset cache when the schema 
rename change is
@@ -958,7 +962,7 @@ public class FilesetCatalogOperations extends 
ManagedSchemaOperations
       LOG.warn("Schema {} does not exist", ident);
       return false;
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to delete schema " + ident + " 
location", ioe);
+      throw ExceptionMessages.wrap("Failed to delete schema " + ident + " 
location", ioe);
     }
   }
 
@@ -1118,7 +1122,7 @@ public class FilesetCatalogOperations extends 
ManagedSchemaOperations
                           + locationName);
                 }
               } catch (IOException e) {
-                throw new RuntimeException(
+                throw ExceptionMessages.wrap(
                     "Failed to check if fileset catalog location exists: " + 
v, e);
               }
             }
@@ -1450,7 +1454,7 @@ public class FilesetCatalogOperations extends 
ManagedSchemaOperations
           "Interrupted when getting FileSystem for path: {}, possibly the 
server is"
               + " shutting down or catalog is been dropped",
           path);
-      throw new RuntimeException("Interrupted when getting FileSystem for 
path: " + path, e);
+      throw ExceptionMessages.wrap("Interrupted when getting FileSystem for 
path: " + path, e);
     } catch (ExecutionException e) {
       Throwable cause = e.getCause();
       if (cause instanceof IOException) {
diff --git 
a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueExceptionConverter.java
 
b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueExceptionConverter.java
index 544726bf5b..4ab3ed9c9b 100644
--- 
a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueExceptionConverter.java
+++ 
b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueExceptionConverter.java
@@ -19,12 +19,15 @@
 package org.apache.gravitino.catalog.glue;
 
 import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.exceptions.ForbiddenException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
 import org.apache.gravitino.exceptions.SchemaAlreadyExistsException;
 import org.apache.gravitino.exceptions.TableAlreadyExistsException;
+import org.apache.gravitino.utils.ExceptionMessages;
 import software.amazon.awssdk.awscore.exception.AwsErrorDetails;
 import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.services.glue.model.AccessDeniedException;
 import software.amazon.awssdk.services.glue.model.AlreadyExistsException;
 import software.amazon.awssdk.services.glue.model.EntityNotFoundException;
 import software.amazon.awssdk.services.glue.model.GlueException;
@@ -84,7 +87,10 @@ final class GlueExceptionConverter {
       return new SchemaAlreadyExistsException(e, "%s already exists", context);
     }
     if (e instanceof InvalidInputException) {
-      return new IllegalArgumentException(context + ": " + e.getMessage(), e);
+      return ExceptionMessages.illegalArgument(context, e);
+    }
+    if (e instanceof AccessDeniedException) {
+      return new ForbiddenException(e, "Glue error: %s: %s", context, 
awsErrorDetail(e));
     }
     return new RuntimeException("Glue error: " + context + ": " + 
awsErrorDetail(e), e);
   }
@@ -104,7 +110,10 @@ final class GlueExceptionConverter {
       return new TableAlreadyExistsException(e, "%s already exists", context);
     }
     if (e instanceof InvalidInputException) {
-      return new IllegalArgumentException(context + ": " + e.getMessage(), e);
+      return ExceptionMessages.illegalArgument(context, e);
+    }
+    if (e instanceof AccessDeniedException) {
+      return new ForbiddenException(e, "Glue error: %s: %s", context, 
awsErrorDetail(e));
     }
     return new RuntimeException("Glue error: " + context + ": " + 
awsErrorDetail(e), e);
   }
diff --git 
a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueTableOperations.java
 
b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueTableOperations.java
index 4891932f29..7b5af82f49 100644
--- 
a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueTableOperations.java
+++ 
b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueTableOperations.java
@@ -32,6 +32,7 @@ import org.apache.gravitino.rel.expressions.literals.Literals;
 import org.apache.gravitino.rel.partitions.IdentityPartition;
 import org.apache.gravitino.rel.partitions.Partition;
 import org.apache.gravitino.rel.partitions.Partitions;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import software.amazon.awssdk.services.glue.GlueClient;
@@ -99,7 +100,7 @@ class GlueTableOperations implements TableOperations, 
SupportsPartitions {
         nextToken = resp.nextToken();
       } while (nextToken != null);
     } catch (GlueException e) {
-      throw new RuntimeException("Failed to list partitions for table " + 
tableName, e);
+      throw ExceptionMessages.wrap("Failed to list partitions for table " + 
tableName, e);
     }
     return names.toArray(new String[0]);
   }
@@ -121,7 +122,7 @@ class GlueTableOperations implements TableOperations, 
SupportsPartitions {
         nextToken = resp.nextToken();
       } while (nextToken != null);
     } catch (GlueException e) {
-      throw new RuntimeException("Failed to list partitions for table " + 
tableName, e);
+      throw ExceptionMessages.wrap("Failed to list partitions for table " + 
tableName, e);
     }
     return partitions.toArray(new Partition[0]);
   }
@@ -141,7 +142,7 @@ class GlueTableOperations implements TableOperations, 
SupportsPartitions {
       throw new NoSuchPartitionException(
           e, "Partition %s does not exist in table %s", partitionName, 
tableName);
     } catch (GlueException e) {
-      throw new RuntimeException("Failed to get partition " + partitionName, 
e);
+      throw ExceptionMessages.wrap("Failed to get partition " + partitionName, 
e);
     }
   }
 
@@ -181,7 +182,7 @@ class GlueTableOperations implements TableOperations, 
SupportsPartitions {
       throw new PartitionAlreadyExistsException(
           e, "Partition %s already exists in table %s", partition.name(), 
tableName);
     } catch (GlueException e) {
-      throw new RuntimeException("Failed to add partition " + 
partition.name(), e);
+      throw ExceptionMessages.wrap("Failed to add partition " + 
partition.name(), e);
     }
 
     LOG.info("Added partition {} to {}.{}", partition.name(), dbName, 
tableName);
@@ -210,7 +211,7 @@ class GlueTableOperations implements TableOperations, 
SupportsPartitions {
     } catch (EntityNotFoundException e) {
       return false;
     } catch (GlueException e) {
-      throw new RuntimeException("Failed to drop partition " + partitionName, 
e);
+      throw ExceptionMessages.wrap("Failed to drop partition " + 
partitionName, e);
     }
   }
 
diff --git 
a/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueExceptionConverter.java
 
b/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueExceptionConverter.java
index 33ff92a56e..23a32114a7 100644
--- 
a/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueExceptionConverter.java
+++ 
b/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueExceptionConverter.java
@@ -24,6 +24,7 @@ import static 
org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import org.apache.gravitino.exceptions.ForbiddenException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
 import org.apache.gravitino.exceptions.SchemaAlreadyExistsException;
@@ -97,7 +98,7 @@ public class TestGlueExceptionConverter {
 
     RuntimeException converted = GlueExceptionConverter.toSchemaException(e, 
"schema drop_me");
 
-    assertEquals(RuntimeException.class, converted.getClass());
+    assertInstanceOf(ForbiddenException.class, converted);
     assertSame(e, converted.getCause());
     String message = converted.getMessage();
     assertTrue(message.contains("schema drop_me"), message);
@@ -120,7 +121,7 @@ public class TestGlueExceptionConverter {
 
     RuntimeException converted = GlueExceptionConverter.toTableException(e, 
"table ctas_test");
 
-    assertEquals(RuntimeException.class, converted.getClass());
+    assertInstanceOf(ForbiddenException.class, converted);
     assertSame(e, converted.getCause());
     String message = converted.getMessage();
     assertTrue(message.contains("table ctas_test"), message);
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 cd73a9eed4..c3751a6c37 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
@@ -89,6 +89,7 @@ import 
org.apache.gravitino.rel.expressions.transforms.Transforms;
 import org.apache.gravitino.rel.indexes.Index;
 import org.apache.gravitino.rel.types.Type;
 import org.apache.gravitino.utils.ClassLoaderResourceCleanerUtils;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.apache.gravitino.utils.PrincipalUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -493,7 +494,7 @@ public class HiveCatalogOperations
       return new HiveTableHandle(table, clientPool);
 
     } catch (InterruptedException e) {
-      throw new RuntimeException(
+      throw ExceptionMessages.wrap(
           "Failed to load Hive table " + tableIdent.name() + " from Hive 
metastore", e);
     }
   }
diff --git 
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveTableOperations.java
 
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveTableOperations.java
index 5ba130a8f3..bf78f57f3e 100644
--- 
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveTableOperations.java
+++ 
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveTableOperations.java
@@ -36,6 +36,7 @@ import org.apache.gravitino.hive.HiveTable;
 import org.apache.gravitino.rel.SupportsPartitions;
 import org.apache.gravitino.rel.partitions.IdentityPartition;
 import org.apache.gravitino.rel.partitions.Partition;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -56,7 +57,7 @@ public class HiveTableOperations implements TableOperations, 
SupportsPartitions
           .clientPool()
           .run(c -> c.listPartitionNames(tableHandle.table(), (short) 
-1).toArray(new String[0]));
     } catch (InterruptedException e) {
-      throw new RuntimeException(
+      throw ExceptionMessages.wrap(
           "Failed to list partition names of table " + tableHandle.name() + 
"from Hive Metastore",
           e);
     }
@@ -70,7 +71,7 @@ public class HiveTableOperations implements TableOperations, 
SupportsPartitions
           .run(c -> c.listPartitions(tableHandle.table(), (short) -1))
           .toArray(new Partition[0]);
     } catch (InterruptedException e) {
-      throw new RuntimeException(
+      throw ExceptionMessages.wrap(
           "Failed to list partitions of table " + tableHandle.name() + "from 
Hive Metastore", e);
     }
   }
@@ -81,7 +82,7 @@ public class HiveTableOperations implements TableOperations, 
SupportsPartitions
       return tableHandle.clientPool().run(c -> 
c.getPartition(tableHandle.table(), partitionName));
 
     } catch (InterruptedException e) {
-      throw new RuntimeException(
+      throw ExceptionMessages.wrap(
           "Failed to get partition "
               + partitionName
               + " of table "
@@ -172,7 +173,7 @@ public class HiveTableOperations implements 
TableOperations, SupportsPartitions
       return false;
 
     } catch (InterruptedException e) {
-      throw new RuntimeException(
+      throw ExceptionMessages.wrap(
           "Failed to get partition "
               + partitionName
               + " of table "
diff --git 
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveViewCatalogOperations.java
 
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveViewCatalogOperations.java
index f3d078ff2e..f571fc4eea 100644
--- 
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveViewCatalogOperations.java
+++ 
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveViewCatalogOperations.java
@@ -50,6 +50,7 @@ import org.apache.gravitino.rel.SQLRepresentation;
 import org.apache.gravitino.rel.View;
 import org.apache.gravitino.rel.ViewCatalog;
 import org.apache.gravitino.rel.ViewChange;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.apache.gravitino.utils.PrincipalUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -90,7 +91,7 @@ class HiveViewCatalogOperations implements ViewCatalog {
           .map(name -> NameIdentifier.of(namespace, name))
           .toArray(NameIdentifier[]::new);
     } catch (InterruptedException e) {
-      throw new RuntimeException("Failed to list Hive views in " + namespace, 
e);
+      throw ExceptionMessages.wrap("Failed to list Hive views in " + 
namespace, e);
     }
   }
 
@@ -157,9 +158,9 @@ class HiveViewCatalogOperations implements ViewCatalog {
     } catch (TableAlreadyExistsException e) {
       throw new ViewAlreadyExistsException(e, "View %s already exists in Hive 
Metastore", ident);
     } catch (InterruptedException e) {
-      throw new RuntimeException("Failed to create Hive view " + ident, e);
+      throw ExceptionMessages.wrap("Failed to create Hive view " + ident, e);
     } catch (Exception e) {
-      throw new RuntimeException("Failed to create Hive view " + ident, e);
+      throw ExceptionMessages.wrap("Failed to create Hive view " + ident, e);
     }
   }
 
@@ -264,9 +265,9 @@ class HiveViewCatalogOperations implements ViewCatalog {
     } catch (UnsupportedOperationException e) {
       throw e;
     } catch (InterruptedException e) {
-      throw new RuntimeException("Failed to alter Hive view " + ident, e);
+      throw ExceptionMessages.wrap("Failed to alter Hive view " + ident, e);
     } catch (Exception e) {
-      throw new RuntimeException("Failed to alter Hive view " + ident, e);
+      throw ExceptionMessages.wrap("Failed to alter Hive view " + ident, e);
     }
   }
 
@@ -311,9 +312,9 @@ class HiveViewCatalogOperations implements ViewCatalog {
     } catch (NoSuchTableException e) {
       return false;
     } catch (InterruptedException e) {
-      throw new RuntimeException("Failed to drop Hive view " + ident, e);
+      throw ExceptionMessages.wrap("Failed to drop Hive view " + ident, e);
     } catch (Exception e) {
-      throw new RuntimeException("Failed to drop Hive view " + ident, e);
+      throw ExceptionMessages.wrap("Failed to drop Hive view " + ident, e);
     }
   }
 
@@ -349,9 +350,9 @@ class HiveViewCatalogOperations implements ViewCatalog {
     } catch (NoSuchTableException e) {
       throw new NoSuchViewException(e, "View %s does not exist in Hive 
Metastore", ident);
     } catch (InterruptedException e) {
-      throw new RuntimeException("Failed to load Hive view " + ident, e);
+      throw ExceptionMessages.wrap("Failed to load Hive view " + ident, e);
     } catch (Exception e) {
-      throw new RuntimeException("Failed to load Hive view " + ident, e);
+      throw ExceptionMessages.wrap("Failed to load Hive view " + ident, e);
     }
   }
 
diff --git 
a/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java
 
b/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java
index b002af801b..8d0bf8464a 100644
--- 
a/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java
+++ 
b/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java
@@ -70,6 +70,7 @@ import org.apache.gravitino.rel.indexes.Index;
 import org.apache.gravitino.rel.indexes.Indexes;
 import org.apache.gravitino.rel.partitions.ListPartition;
 import org.apache.gravitino.rel.partitions.RangePartition;
+import org.apache.gravitino.utils.ExceptionMessages;
 
 /** Table operations for Apache Doris. */
 public class DorisTableOperations extends JdbcTableOperations {
@@ -215,7 +216,7 @@ public class DorisTableOperations extends 
JdbcTableOperations {
                   .toString());
         }
       } catch (Exception e) {
-        throw new RuntimeException("Failed to get the number of backend 
servers", e);
+        throw ExceptionMessages.wrap("Failed to get the number of backend 
servers", e);
       }
     }
 
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 980b92b542..85a5feaa29 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
@@ -66,6 +66,7 @@ import org.apache.gravitino.messaging.TopicChange;
 import org.apache.gravitino.meta.AuditInfo;
 import org.apache.gravitino.meta.SchemaEntity;
 import org.apache.gravitino.storage.IdGenerator;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.apache.gravitino.utils.NamespaceUtil;
 import org.apache.gravitino.utils.PrincipalUtils;
 import org.apache.kafka.clients.admin.AdminClient;
@@ -153,10 +154,9 @@ public class KafkaCatalogOperations implements 
CatalogOperations, SupportsSchema
       adminClient = AdminClient.create(adminClientConfig);
     } catch (KafkaException e) {
       if (e.getCause() instanceof ConfigException) {
-        throw new IllegalArgumentException(
-            "Invalid configuration for Kafka AdminClient: " + 
e.getCause().getMessage(), e);
+        throw ExceptionMessages.illegalArgument("Invalid configuration for 
Kafka AdminClient", e);
       }
-      throw new RuntimeException("Failed to create Kafka AdminClient", e);
+      throw ExceptionMessages.wrap("Failed to create Kafka AdminClient", e);
     }
     createDefaultSchemaIfNecessary();
   }
@@ -173,13 +173,9 @@ public class KafkaCatalogOperations implements 
CatalogOperations, SupportsSchema
           .map(name -> NameIdentifier.of(namespace, name))
           .toArray(NameIdentifier[]::new);
     } catch (ExecutionException e) {
-      throw new RuntimeException(
-          String.format(
-              "Failed to list topics under the schema %s: %s",
-              namespace, e.getCause().getMessage()),
-          e);
+      throw ExceptionMessages.wrap("Failed to list topics under the schema " + 
namespace, e);
     } catch (InterruptedException e) {
-      throw new RuntimeException("Failed to list topics under the schema " + 
namespace, e);
+      throw ExceptionMessages.wrap("Failed to list topics under the schema " + 
namespace, e);
     }
   }
 
@@ -226,10 +222,10 @@ public class KafkaCatalogOperations implements 
CatalogOperations, SupportsSchema
       if (e.getCause() instanceof UnknownTopicOrPartitionException) {
         throw new NoSuchTopicException(e, "Topic %s does not exist", ident);
       } else {
-        throw new RuntimeException("Failed to load topic " + ident.name() + " 
from Kafka", e);
+        throw ExceptionMessages.wrap("Failed to load topic " + ident.name() + 
" from Kafka", e);
       }
     } catch (InterruptedException e) {
-      throw new RuntimeException("Failed to load topic " + ident.name() + " 
from Kafka", e);
+      throw ExceptionMessages.wrap("Failed to load topic " + ident.name() + " 
from Kafka", e);
     }
 
     LOG.info("Loaded topic {} from Kafka", ident);
@@ -297,18 +293,16 @@ public class KafkaCatalogOperations implements 
CatalogOperations, SupportsSchema
         throw new TopicAlreadyExistsException(e, "Topic %s already exists", 
ident);
 
       } else if (e.getCause() instanceof InvalidReplicationFactorException) {
-        throw new IllegalArgumentException(
-            "Invalid replication factor for topic " + ident + 
e.getCause().getMessage(), e);
+        throw ExceptionMessages.illegalArgument("Invalid replication factor 
for topic " + ident, e);
 
       } else if (e.getCause() instanceof InvalidConfigurationException) {
-        throw new IllegalArgumentException(
-            "Invalid properties for topic " + ident + 
e.getCause().getMessage(), e);
+        throw ExceptionMessages.illegalArgument("Invalid properties for topic 
" + ident, e);
 
       } else {
-        throw new RuntimeException("Failed to create topic in Kafka" + ident, 
e);
+        throw ExceptionMessages.wrap("Failed to create topic in Kafka " + 
ident, e);
       }
     } catch (InterruptedException e) {
-      throw new RuntimeException("Failed to create topic in Kafka" + ident, e);
+      throw ExceptionMessages.wrap("Failed to create topic in Kafka " + ident, 
e);
     }
   }
 
@@ -381,10 +375,10 @@ public class KafkaCatalogOperations implements 
CatalogOperations, SupportsSchema
       if (e.getCause() instanceof UnknownTopicOrPartitionException) {
         return false;
       } else {
-        throw new RuntimeException("Failed to drop topic " + ident.name() + " 
from Kafka", e);
+        throw ExceptionMessages.wrap("Failed to drop topic " + ident.name() + 
" from Kafka", e);
       }
     } catch (InterruptedException e) {
-      throw new RuntimeException("Failed to drop topic " + ident.name() + " 
from Kafka", e);
+      throw ExceptionMessages.wrap("Failed to drop topic " + ident.name() + " 
from Kafka", e);
     }
   }
 
@@ -397,7 +391,7 @@ public class KafkaCatalogOperations implements 
CatalogOperations, SupportsSchema
           .map(s -> NameIdentifier.of(namespace, s.name()))
           .toArray(NameIdentifier[]::new);
     } catch (IOException e) {
-      throw new RuntimeException("Failed to list schemas under namespace " + 
namespace, e);
+      throw ExceptionMessages.wrap("Failed to list schemas under namespace " + 
namespace, e);
     }
   }
 
@@ -426,7 +420,7 @@ public class KafkaCatalogOperations implements 
CatalogOperations, SupportsSchema
     } catch (NoSuchEntityException exception) {
       throw new NoSuchSchemaException(exception, "Schema %s does not exist", 
ident);
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to load schema " + ident, ioe);
+      throw ExceptionMessages.wrap("Failed to load schema " + ident, ioe);
     }
   }
 
@@ -534,8 +528,16 @@ public class KafkaCatalogOperations implements 
CatalogOperations, SupportsSchema
               Collections.singletonMap(topicName, 
NewPartitions.increaseTo(newPartitionCount)))
           .all()
           .get();
-    } catch (Exception e) {
-      throw new RuntimeException("Failed to increase partition count for topic 
" + topicName, e);
+    } catch (ExecutionException e) {
+      if (e.getCause() instanceof InvalidConfigurationException
+          || e.getCause() instanceof IllegalArgumentException) {
+        throw ExceptionMessages.illegalArgument(
+            "Failed to increase partition count for topic " + topicName, e);
+      }
+      throw ExceptionMessages.wrap("Failed to increase partition count for 
topic " + topicName, e);
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      throw ExceptionMessages.wrap("Failed to increase partition count for 
topic " + topicName, e);
     }
   }
 
@@ -546,10 +548,19 @@ public class KafkaCatalogOperations implements 
CatalogOperations, SupportsSchema
           .incrementalAlterConfigs(Collections.singletonMap(topicResource, 
alterConfigOps))
           .all()
           .get();
-    } catch (UnknownTopicOrPartitionException e) {
-      throw new NoSuchTopicException(e, "Topic %s does not exist", topicName);
-    } catch (Exception e) {
-      throw new RuntimeException("Failed to alter topic properties for topic " 
+ topicName, e);
+    } catch (ExecutionException e) {
+      if (e.getCause() instanceof UnknownTopicOrPartitionException) {
+        throw new NoSuchTopicException(e, "Topic %s does not exist", 
topicName);
+      }
+      if (e.getCause() instanceof InvalidConfigurationException
+          || e.getCause() instanceof IllegalArgumentException) {
+        throw ExceptionMessages.illegalArgument(
+            "Failed to alter topic properties for topic " + topicName, e);
+      }
+      throw ExceptionMessages.wrap("Failed to alter topic properties for topic 
" + topicName, e);
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      throw ExceptionMessages.wrap("Failed to alter topic properties for topic 
" + topicName, e);
     }
   }
 
@@ -590,7 +601,8 @@ public class KafkaCatalogOperations implements 
CatalogOperations, SupportsSchema
         return;
       }
     } catch (IOException e) {
-      throw new RuntimeException("Failed to check if schema " + 
defaultSchemaIdent + " exists", e);
+      throw ExceptionMessages.wrap(
+          "Failed to check if schema " + defaultSchemaIdent + " exists", e);
     }
 
     // Create the default schema
@@ -617,7 +629,7 @@ public class KafkaCatalogOperations implements 
CatalogOperations, SupportsSchema
     try {
       store.put(defaultSchema, true /* overwrite */);
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to create default schema for Kafka 
catalog", ioe);
+      throw ExceptionMessages.wrap("Failed to create default schema for Kafka 
catalog", ioe);
     }
   }
 }
diff --git 
a/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/generic/GenericCatalogOperations.java
 
b/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/generic/GenericCatalogOperations.java
index 445b87dcc4..11497e4d68 100644
--- 
a/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/generic/GenericCatalogOperations.java
+++ 
b/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/generic/GenericCatalogOperations.java
@@ -66,6 +66,7 @@ import org.apache.gravitino.rel.expressions.sorts.SortOrder;
 import org.apache.gravitino.rel.expressions.transforms.Transform;
 import org.apache.gravitino.rel.indexes.Index;
 import org.apache.gravitino.storage.IdGenerator;
+import org.apache.gravitino.utils.ExceptionMessages;
 
 /** Operations for interacting with a generic lakehouse catalog in Apache 
Gravitino. */
 public class GenericCatalogOperations implements CatalogOperations, 
SupportsSchemas, TableCatalog {
@@ -352,11 +353,9 @@ public class GenericCatalogOperations implements 
CatalogOperations, SupportsSche
       } else if (t instanceof IllegalArgumentException) {
         throw (IllegalArgumentException) t;
       } else if (t instanceof IOException) {
-        throw new RuntimeException(
-            String.format("Failed to load table %s: %s", tableIdent, 
t.getMessage()), t);
+        throw ExceptionMessages.wrap("Failed to load table " + tableIdent, t);
       } else {
-        throw new RuntimeException(
-            String.format("Unexpected exception when loading table %s", 
tableIdent), t);
+        throw ExceptionMessages.wrap("Unexpected exception when loading table 
" + tableIdent, t);
       }
     }
   }
diff --git 
a/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/lance/LanceTableOperations.java
 
b/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/lance/LanceTableOperations.java
index dd7dc2b314..fb2bfaba1f 100644
--- 
a/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/lance/LanceTableOperations.java
+++ 
b/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/lance/LanceTableOperations.java
@@ -66,6 +66,7 @@ import 
org.apache.gravitino.rel.expressions.transforms.Transform;
 import org.apache.gravitino.rel.indexes.Index;
 import org.apache.gravitino.storage.IdGenerator;
 import org.apache.gravitino.storage.relational.service.TableMetaService;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.apache.gravitino.utils.PrincipalUtils;
 import org.lance.Dataset;
 import org.lance.ReadOptions;
@@ -303,7 +304,7 @@ public class LanceTableOperations extends 
ManagedTableOperations {
     } catch (NoSuchTableException e) {
       return false;
     } catch (Exception e) {
-      throw new RuntimeException("Failed to purge Lance dataset for table " + 
ident, e);
+      throw ExceptionMessages.wrap("Failed to purge Lance dataset for table " 
+ ident, e);
     }
   }
 
@@ -337,7 +338,7 @@ public class LanceTableOperations extends 
ManagedTableOperations {
     } catch (NoSuchTableException e) {
       return false;
     } catch (Exception e) {
-      throw new RuntimeException("Failed to drop Lance dataset for table " + 
ident, e);
+      throw ExceptionMessages.wrap("Failed to drop Lance dataset for table " + 
ident, e);
     }
   }
 
@@ -355,7 +356,7 @@ public class LanceTableOperations extends 
ManagedTableOperations {
           && e.getMessage().contains("Not found:")) {
         LOG.warn("Lance dataset at {} was already deleted, skipping.", 
location);
       } else {
-        throw new RuntimeException("Failed to delete Lance dataset at " + 
location, e);
+        throw ExceptionMessages.wrap("Failed to delete Lance dataset at " + 
location, e);
       }
     }
   }
@@ -433,7 +434,7 @@ public class LanceTableOperations extends 
ManagedTableOperations {
       }
       throw e;
     } catch (Exception e) {
-      throw new RuntimeException("Failed to create Lance dataset at location " 
+ location, e);
+      throw ExceptionMessages.wrap("Failed to create Lance dataset at location 
" + location, e);
     }
   }
 
@@ -491,7 +492,9 @@ public class LanceTableOperations extends 
ManagedTableOperations {
     } catch (Exception e) {
       if (forAlter) {
         throw new IllegalStateException(
-            "Failed to load Lance schema before altering table " + ident, e);
+            ExceptionMessages.withCause(
+                "Failed to load Lance schema before altering table " + ident, 
e),
+            e);
       }
       LOG.debug(
           "Failed to load Lance schema from location {} for table {}. Return 
stored metadata.",
@@ -577,9 +580,9 @@ public class LanceTableOperations extends 
ManagedTableOperations {
     } catch (NoSuchEntityException e) {
       throw new NoSuchTableException(e, "Table %s does not exist", ident);
     } catch (EntityAlreadyExistsException e) {
-      throw new IllegalArgumentException("Failed to repair table " + ident, e);
+      throw ExceptionMessages.illegalArgument("Failed to repair table " + 
ident, e);
     } catch (IOException e) {
-      throw new RuntimeException("Failed to repair table " + ident, e);
+      throw ExceptionMessages.wrap("Failed to repair table " + ident, e);
     }
   }
 
@@ -688,9 +691,10 @@ public class LanceTableOperations extends 
ManagedTableOperations {
     } catch (NoSuchEntityException e) {
       throw new NoSuchTableException(e, "Table %s does not exist", ident);
     } catch (EntityAlreadyExistsException e) {
-      throw new IllegalArgumentException("Failed to record empty version for 
table " + ident, e);
+      throw ExceptionMessages.illegalArgument(
+          "Failed to record empty version for table " + ident, e);
     } catch (IOException e) {
-      throw new RuntimeException("Failed to record empty version for table " + 
ident, e);
+      throw ExceptionMessages.wrap("Failed to record empty version for table " 
+ ident, e);
     }
   }
 
@@ -815,7 +819,7 @@ public class LanceTableOperations extends 
ManagedTableOperations {
     } catch (RuntimeException e) {
       throw e;
     } catch (Exception e) {
-      throw new RuntimeException(
+      throw ExceptionMessages.wrap(
           "Failed to handle alterations to Lance dataset at location " + 
location, e);
     }
   }
diff --git 
a/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/utils/CatalogUtils.java
 
b/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/utils/CatalogUtils.java
index be3febd7d5..dfe5ac749e 100644
--- 
a/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/utils/CatalogUtils.java
+++ 
b/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/utils/CatalogUtils.java
@@ -41,6 +41,7 @@ import 
org.apache.gravitino.catalog.lakehouse.paimon.authentication.Authenticati
 import 
org.apache.gravitino.catalog.lakehouse.paimon.authentication.kerberos.KerberosClient;
 import 
org.apache.gravitino.catalog.lakehouse.paimon.ops.PaimonBackendCatalogWrapper;
 import org.apache.gravitino.exceptions.ConnectionFailedException;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.paimon.catalog.Catalog;
 import org.apache.paimon.catalog.CatalogContext;
@@ -71,7 +72,7 @@ public class CatalogUtils {
         Catalog catalog = loadCatalogBackendWithKerberosAuth(paimonConfig, 
configuration);
         return new PaimonBackendCatalogWrapper(catalog, kerberosClient);
       } catch (Exception e) {
-        throw new RuntimeException("Failed to login with kerberos", e);
+        throw ExceptionMessages.wrap("Failed to login with kerberos", e);
       }
     } else {
       throw new UnsupportedOperationException(
diff --git 
a/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelCatalogOperations.java
 
b/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelCatalogOperations.java
index 1f2d14c6e0..1d0c232366 100644
--- 
a/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelCatalogOperations.java
+++ 
b/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelCatalogOperations.java
@@ -54,6 +54,7 @@ import org.apache.gravitino.model.ModelCatalog;
 import org.apache.gravitino.model.ModelChange;
 import org.apache.gravitino.model.ModelVersion;
 import org.apache.gravitino.model.ModelVersionChange;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.apache.gravitino.utils.NameIdentifierUtil;
 import org.apache.gravitino.utils.NamespaceUtil;
 import org.apache.gravitino.utils.PrincipalUtils;
@@ -101,7 +102,7 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
     } catch (NoSuchEntityException e) {
       throw new NoSuchSchemaException(e, "Schema %s does not exist", 
namespace);
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to list models under namespace " + 
namespace, ioe);
+      throw ExceptionMessages.wrap("Failed to list models under namespace " + 
namespace, ioe);
     }
   }
 
@@ -116,7 +117,7 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
     } catch (NoSuchEntityException e) {
       throw new NoSuchModelException(e, "Model %s does not exist", ident);
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to get model " + ident, ioe);
+      throw ExceptionMessages.wrap("Failed to get model " + ident, ioe);
     }
   }
 
@@ -146,7 +147,7 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
     try {
       store.put(model, false /* overwrite */);
     } catch (IOException e) {
-      throw new RuntimeException("Failed to register model " + ident, e);
+      throw ExceptionMessages.wrap("Failed to register model " + ident, e);
     } catch (EntityAlreadyExistsException e) {
       throw new ModelAlreadyExistsException(e, "Model %s already exists", 
ident);
     } catch (NoSuchEntityException e) {
@@ -163,7 +164,7 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
     try {
       return store.delete(ident, Entity.EntityType.MODEL);
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to delete model " + ident, ioe);
+      throw ExceptionMessages.wrap("Failed to delete model " + ident, ioe);
     }
   }
 
@@ -180,7 +181,7 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
     } catch (NoSuchEntityException e) {
       throw new NoSuchModelException(e, "Model %s does not exist", ident);
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to list model versions for model " + 
ident, ioe);
+      throw ExceptionMessages.wrap("Failed to list model versions for model " 
+ ident, ioe);
     }
   }
 
@@ -197,7 +198,7 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
     } catch (NoSuchEntityException e) {
       throw new NoSuchModelException(e, "Model %s does not exist", ident);
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to list model version infos for model 
" + ident, ioe);
+      throw ExceptionMessages.wrap("Failed to list model version infos for 
model " + ident, ioe);
     }
   }
 
@@ -254,7 +255,7 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
     try {
       store.put(modelVersion, false /* overwrite */);
     } catch (IOException e) {
-      throw new RuntimeException("Failed to link model version " + ident, e);
+      throw ExceptionMessages.wrap("Failed to link model version " + ident, e);
     } catch (EntityAlreadyExistsException e) {
       throw new ModelVersionAliasesAlreadyExistException(
           e, "Model version alias already exists in %s", ident);
@@ -306,7 +307,7 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
         throw new NoSuchModelException("Model %s does not exist", ident);
       }
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to alter model " + ident, ioe);
+      throw ExceptionMessages.wrap("Failed to alter model " + ident, ioe);
     }
 
     try {
@@ -320,12 +321,12 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
       return toModelImpl(updatedModelEntity);
 
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to load model " + ident, ioe);
+      throw ExceptionMessages.wrap("Failed to load model " + ident, ioe);
     } catch (NoSuchEntityException nsee) {
       throw new NoSuchModelException(nsee, "Model %s does not exist", ident);
     } catch (EntityAlreadyExistsException eaee) {
       // This is happened when renaming a model to an existing model name.
-      throw new RuntimeException("Model already exist " + ident.name(), eaee);
+      throw ExceptionMessages.wrap("Model already exist " + ident.name(), 
eaee);
     }
   }
 
@@ -416,7 +417,7 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
         throw new NoSuchModelVersionException("Model version %s does not 
exist", ident);
       }
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to alter model version " + ident, 
ioe);
+      throw ExceptionMessages.wrap("Failed to alter model version " + ident, 
ioe);
     }
 
     try {
@@ -430,7 +431,7 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
       return toModelVersionImpl(updatedModelVersionEntity);
 
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to load model version " + ident, ioe);
+      throw ExceptionMessages.wrap("Failed to load model version " + ident, 
ioe);
     } catch (NoSuchEntityException nsee) {
       throw new NoSuchModelVersionException(nsee, "Model Version %s does not 
exist", ident);
     }
@@ -546,7 +547,7 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
     } catch (NoSuchEntityException e) {
       throw new NoSuchModelVersionException(e, "Model version %s does not 
exist", ident);
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to get model version " + ident, ioe);
+      throw ExceptionMessages.wrap("Failed to get model version " + ident, 
ioe);
     }
   }
 
@@ -597,7 +598,7 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
     try {
       return store.delete(ident, Entity.EntityType.MODEL_VERSION);
     } catch (IOException ioe) {
-      throw new RuntimeException("Failed to delete model version " + ident, 
ioe);
+      throw ExceptionMessages.wrap("Failed to delete model version " + ident, 
ioe);
     }
   }
 
diff --git 
a/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/FileSystemUtils.java
 
b/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/FileSystemUtils.java
index 0c1b00cbee..10265761d5 100644
--- 
a/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/FileSystemUtils.java
+++ 
b/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/FileSystemUtils.java
@@ -32,6 +32,7 @@ import java.util.ServiceLoader;
 import javax.xml.stream.XMLOutputFactory;
 import javax.xml.stream.XMLStreamWriter;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.Path;
 
@@ -158,7 +159,7 @@ public class FileSystemUtils {
       gravitinoFileSystemCredentialsProvider.setConf(conf);
       return gravitinoFileSystemCredentialsProvider;
     } catch (Exception e) {
-      throw new RuntimeException("Failed to create 
GravitinoFileSystemCredentialProvider", e);
+      throw ExceptionMessages.wrap("Failed to create 
GravitinoFileSystemCredentialProvider", e);
     }
   }
 
@@ -220,7 +221,7 @@ public class FileSystemUtils {
 
       return configuration;
     } catch (Exception e) {
-      throw new RuntimeException("Failed to create configuration", e);
+      throw ExceptionMessages.wrap("Failed to create configuration", e);
     }
   }
 
@@ -231,7 +232,7 @@ public class FileSystemUtils {
       writeElement(writer, VALUE_TAG, value);
       writer.writeEndElement();
     } catch (Exception e) {
-      throw new RuntimeException("Failed to write property: " + key, e);
+      throw ExceptionMessages.wrap("Failed to write property: " + key, e);
     }
   }
 
diff --git 
a/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/HDFSFileSystemProxy.java
 
b/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/HDFSFileSystemProxy.java
index cf27d04903..2b26356dae 100644
--- 
a/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/HDFSFileSystemProxy.java
+++ 
b/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/HDFSFileSystemProxy.java
@@ -37,6 +37,7 @@ import net.sf.cglib.proxy.MethodProxy;
 import org.apache.gravitino.catalog.hadoop.fs.kerberos.AuthenticationConfig;
 import org.apache.gravitino.catalog.hadoop.fs.kerberos.KerberosClient;
 import org.apache.gravitino.exceptions.GravitinoRuntimeException;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
@@ -157,7 +158,7 @@ public class HDFSFileSystemProxy implements 
MethodInterceptor {
                 if (RuntimeException.class.isAssignableFrom(e.getClass())) {
                   throw (RuntimeException) e;
                 }
-                throw new RuntimeException("Failed to invoke method", e);
+                throw ExceptionMessages.wrap("Failed to invoke method", e);
               }
             });
   }
@@ -175,7 +176,7 @@ public class HDFSFileSystemProxy implements 
MethodInterceptor {
       this.kerberosRealm = client.getKerberosRealm();
       return ugi;
     } catch (IOException e) {
-      throw new RuntimeException("Failed to login with Kerberos", e);
+      throw ExceptionMessages.wrap("Failed to login with Kerberos", e);
     }
   }
 }
diff --git 
a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveClientFactory.java
 
b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveClientFactory.java
index 544df5e195..45d6b7cfd8 100644
--- 
a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveClientFactory.java
+++ 
b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveClientFactory.java
@@ -34,6 +34,7 @@ import 
org.apache.gravitino.exceptions.GravitinoRuntimeException;
 import org.apache.gravitino.hive.kerberos.AuthenticationConfig;
 import org.apache.gravitino.hive.kerberos.KerberosClient;
 import org.apache.gravitino.utils.ClassLoaderResourceCleanerUtils;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.apache.gravitino.utils.PrincipalUtils;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.security.UserGroupInformation;
@@ -110,7 +111,7 @@ public final class HiveClientFactory {
           "Failed to connect to Hive Metastore using cached Hive version {}",
           classLoader.getHiveVersion(),
           e);
-      throw new RuntimeException("Failed to connect to Hive Metastore", e);
+      throw ExceptionMessages.wrap("Failed to connect to Hive Metastore", e);
     }
   }
 
@@ -247,7 +248,7 @@ public final class HiveClientFactory {
       kerberosClient.login();
 
     } catch (Exception e) {
-      throw new RuntimeException("Failed to initialize kerberos client", e);
+      throw ExceptionMessages.wrap("Failed to initialize kerberos client", e);
     }
   }
 
diff --git 
a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveClientImpl.java
 
b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveClientImpl.java
index 550d730567..1f7818a9c8 100644
--- 
a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveClientImpl.java
+++ 
b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveClientImpl.java
@@ -23,6 +23,7 @@ import java.util.Properties;
 import org.apache.gravitino.hive.HivePartition;
 import org.apache.gravitino.hive.HiveSchema;
 import org.apache.gravitino.hive.HiveTable;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.apache.hadoop.security.UserGroupInformation;
 
 /**
@@ -184,7 +185,7 @@ public class HiveClientImpl implements HiveClient {
     try {
       shim.close();
     } catch (Exception e) {
-      throw new RuntimeException("Failed to close HiveClient", e);
+      throw ExceptionMessages.wrap("Failed to close HiveClient", e);
     }
   }
 
@@ -193,7 +194,7 @@ public class HiveClientImpl implements HiveClient {
     try {
       return UserGroupInformation.getCurrentUser();
     } catch (Exception e) {
-      throw new RuntimeException("Failed to get current user", e);
+      throw ExceptionMessages.wrap("Failed to get current user", e);
     }
   }
 }
diff --git 
a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveExceptionConverter.java
 
b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveExceptionConverter.java
index 3eff0eaae4..cc262a6f51 100644
--- 
a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveExceptionConverter.java
+++ 
b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveExceptionConverter.java
@@ -33,6 +33,7 @@ import 
org.apache.gravitino.exceptions.NonEmptySchemaException;
 import org.apache.gravitino.exceptions.PartitionAlreadyExistsException;
 import org.apache.gravitino.exceptions.SchemaAlreadyExistsException;
 import org.apache.gravitino.exceptions.TableAlreadyExistsException;
+import org.apache.gravitino.utils.ExceptionMessages;
 
 /**
  * Utility class to convert Hive exceptions to Gravitino exceptions. This 
class handles various
@@ -190,7 +191,10 @@ public class HiveExceptionConverter {
 
     if (isConnectionKeyword(lowerMessage) || 
exceptionClassName.contains("TransportException")) {
       return new ConnectionFailedException(
-          cause, "Failed to connect to Hive Metastore: %s", target.name());
+          cause,
+          "%s",
+          ExceptionMessages.withCause(
+              "Failed to connect to Hive Metastore: " + target.name(), cause));
     }
 
     if (cause instanceof RuntimeException) {
diff --git 
a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/ProxyHiveClientImpl.java
 
b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/ProxyHiveClientImpl.java
index 6363148cdf..bf84b4a430 100644
--- 
a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/ProxyHiveClientImpl.java
+++ 
b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/ProxyHiveClientImpl.java
@@ -27,6 +27,7 @@ import java.lang.reflect.UndeclaredThrowableException;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
 import java.util.Properties;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.apache.hadoop.security.UserGroupInformation;
 
 /**
@@ -66,7 +67,7 @@ public class ProxyHiveClientImpl implements InvocationHandler 
{
               new ProxyHiveClientImpl(client, ugi));
 
     } catch (IOException | InterruptedException ex) {
-      throw new RuntimeException("Failed to create Kerberos Hive client", ex);
+      throw ExceptionMessages.wrap("Failed to create Kerberos Hive client", 
ex);
     }
   }
 
diff --git 
a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/Util.java
 
b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/Util.java
index 4cf18cb95f..0b7c88ef9b 100644
--- 
a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/Util.java
+++ 
b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/Util.java
@@ -26,6 +26,7 @@ import java.util.Arrays;
 import java.util.Properties;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.Path;
 import org.slf4j.Logger;
@@ -53,7 +54,7 @@ public class Util {
       properties.forEach((k, v) -> config.set(k.toString(), v.toString()));
       resolveMetastoreUriHosts(config);
     } catch (Exception e) {
-      throw new RuntimeException("Failed to create configuration", e);
+      throw ExceptionMessages.wrap("Failed to create configuration", e);
     }
   }
 
diff --git 
a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/kerberos/KerberosClient.java
 
b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/kerberos/KerberosClient.java
index 57ed6949e1..8be06ff787 100644
--- 
a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/kerberos/KerberosClient.java
+++ 
b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/kerberos/KerberosClient.java
@@ -36,6 +36,7 @@ import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.gravitino.hive.client.HiveClient;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.apache.gravitino.utils.FileFetcher;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hive.thrift.DelegationTokenIdentifier;
@@ -111,7 +112,7 @@ public class KerberosClient implements java.io.Closeable {
 
       return proxyUser;
     } catch (Exception e) {
-      throw new RuntimeException("Failed to create proxy user for Kerberos 
Hive client", e);
+      throw ExceptionMessages.wrap("Failed to create proxy user for Kerberos 
Hive client", e);
     }
   }
 
diff --git 
a/common/src/main/java/org/apache/gravitino/utils/ExceptionMessages.java 
b/common/src/main/java/org/apache/gravitino/utils/ExceptionMessages.java
new file mode 100644
index 0000000000..4818c3c6b1
--- /dev/null
+++ b/common/src/main/java/org/apache/gravitino/utils/ExceptionMessages.java
@@ -0,0 +1,155 @@
+/*
+ * 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.utils;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Set;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import javax.annotation.Nullable;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Helpers for preserving underlying system error messages when wrapping 
exceptions.
+ *
+ * <p>Catalog and server code often adds operation context when rethrowing. 
Context is useful, but
+ * it must not replace the upstream message that operators need to act on.
+ */
+public final class ExceptionMessages {
+
+  private ExceptionMessages() {}
+
+  /**
+   * Returns a non-blank diagnostic message from {@code throwable} or its 
cause chain.
+   *
+   * <p>Transparent wrappers such as {@link ExecutionException} are skipped 
when selecting candidate
+   * messages. Among remaining frames, the shallowest non-blank message is 
preferred so nested
+   * {@link #wrap(String, Throwable)} / {@link #withCause(String, Throwable)} 
context is not
+   * discarded. When a deeper non-blank reason is not already contained in 
that message, it is
+   * appended.
+   *
+   * @param throwable the throwable to inspect, may be null
+   * @return a useful message, or null if none is available
+   */
+  @Nullable
+  public static String usefulMessage(@Nullable Throwable throwable) {
+    if (throwable == null) {
+      return null;
+    }
+
+    Set<Throwable> visited = Collections.newSetFromMap(new 
IdentityHashMap<>());
+    String firstUseful = null;
+    String lastUseful = null;
+    Throwable current = throwable;
+    while (current != null) {
+      if (!visited.add(current)) {
+        break;
+      }
+
+      Throwable cause = current.getCause();
+      if (!isTransparentWrapper(current)) {
+        String message = current.getMessage();
+        // new RuntimeException(executionException) copies cause.toString() as 
the detail
+        // message; ignore that synthetic text and keep walking into the real 
cause.
+        boolean syntheticTransparentMessage =
+            cause != null
+                && isTransparentWrapper(cause)
+                && message != null
+                && message.equals(cause.toString());
+        if (StringUtils.isNotBlank(message) && !syntheticTransparentMessage) {
+          if (firstUseful == null) {
+            firstUseful = message;
+          }
+          lastUseful = message;
+        }
+      }
+
+      if (cause == null || cause == current) {
+        break;
+      }
+      current = cause;
+    }
+
+    if (firstUseful == null) {
+      return null;
+    }
+    if (lastUseful == null || firstUseful.equals(lastUseful) || 
firstUseful.contains(lastUseful)) {
+      return firstUseful;
+    }
+    return firstUseful + ": " + lastUseful;
+  }
+
+  /**
+   * Combines operation context with the underlying cause message.
+   *
+   * <p>If {@code context} already contains the useful cause message, {@code 
context} is returned
+   * unchanged. If there is no useful cause message, {@code context} is 
returned as-is.
+   *
+   * @param context operation/object context such as {@code "Failed to alter 
topic X"}
+   * @param throwable the underlying failure
+   * @return a message that preserves both context and the upstream reason 
when available
+   */
+  public static String withCause(String context, @Nullable Throwable 
throwable) {
+    String useful = usefulMessage(throwable);
+    if (StringUtils.isBlank(useful)) {
+      return context;
+    }
+    if (StringUtils.isBlank(context)) {
+      return useful;
+    }
+    if (context.contains(useful)) {
+      return context;
+    }
+    return context + ": " + useful;
+  }
+
+  /**
+   * Wraps {@code throwable} in a {@link RuntimeException} whose message 
includes both {@code
+   * context} and the underlying cause message.
+   *
+   * @param context operation/object context
+   * @param throwable the underlying failure
+   * @return a runtime exception suitable for rethrowing from catalog 
operations
+   */
+  public static RuntimeException wrap(String context, Throwable throwable) {
+    return new RuntimeException(withCause(context, throwable), throwable);
+  }
+
+  /**
+   * Creates an {@link IllegalArgumentException} whose message includes both 
{@code context} and the
+   * underlying cause message.
+   *
+   * @param context operation/object context
+   * @param throwable the underlying failure
+   * @return an illegal-argument exception for client-caused failures
+   */
+  public static IllegalArgumentException illegalArgument(String context, 
Throwable throwable) {
+    return new IllegalArgumentException(withCause(context, throwable), 
throwable);
+  }
+
+  private static boolean isTransparentWrapper(Throwable throwable) {
+    return throwable instanceof ExecutionException
+        || throwable instanceof CompletionException
+        || throwable instanceof InvocationTargetException
+        || throwable instanceof UndeclaredThrowableException;
+  }
+}
diff --git 
a/common/src/test/java/org/apache/gravitino/utils/TestExceptionMessages.java 
b/common/src/test/java/org/apache/gravitino/utils/TestExceptionMessages.java
new file mode 100644
index 0000000000..79d8b9ec9a
--- /dev/null
+++ b/common/src/test/java/org/apache/gravitino/utils/TestExceptionMessages.java
@@ -0,0 +1,143 @@
+/*
+ * 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.utils;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestExceptionMessages {
+
+  @Test
+  public void testUsefulMessageUnwrapsTransparentWrappers() {
+    Throwable root = new IllegalArgumentException("root reason");
+    Throwable mid = new ExecutionException(root);
+    Throwable top = new RuntimeException(mid);
+
+    Assertions.assertEquals("root reason", 
ExceptionMessages.usefulMessage(top));
+  }
+
+  @Test
+  public void testUsefulMessageKeepsImmediateContextAndAppendsDeeperReason() {
+    Throwable root = new IllegalArgumentException("root reason");
+    Throwable mid = new ExecutionException(root);
+    Throwable top = new RuntimeException("wrapper", mid);
+
+    Assertions.assertEquals("wrapper: root reason", 
ExceptionMessages.usefulMessage(top));
+  }
+
+  @Test
+  public void testUsefulMessageIgnoresBlankMessages() {
+    Throwable blankCause = new IOException("   ");
+    Throwable cause = new IOException("HMS connection refused", blankCause);
+
+    Assertions.assertEquals(
+        "Failed to load table: HMS connection refused",
+        ExceptionMessages.withCause("Failed to load table", cause));
+    Assertions.assertEquals("HMS connection refused", 
ExceptionMessages.usefulMessage(cause));
+  }
+
+  @Test
+  public void testUsefulMessageBlankOnlyChainReturnsNull() {
+    Throwable blank = new IOException("\n\t ");
+    Assertions.assertNull(ExceptionMessages.usefulMessage(blank));
+    Assertions.assertEquals(
+        "Failed to load table", ExceptionMessages.withCause("Failed to load 
table", blank));
+  }
+
+  @Test
+  public void testUsefulMessageStopsOnTwoNodeCycle() {
+    RuntimeException a = new RuntimeException("a");
+    RuntimeException b = new RuntimeException("b");
+    a.initCause(b);
+    b.initCause(a);
+
+    Assertions.assertEquals("a: b", ExceptionMessages.usefulMessage(a));
+  }
+
+  @Test
+  public void testUsefulMessageStopsOnThreeNodeCycle() {
+    RuntimeException a = new RuntimeException("a");
+    RuntimeException b = new RuntimeException("b");
+    RuntimeException c = new RuntimeException("c");
+    a.initCause(b);
+    b.initCause(c);
+    c.initCause(a);
+
+    Assertions.assertEquals("a: c", ExceptionMessages.usefulMessage(a));
+  }
+
+  @Test
+  public void testNestedWrapPreservesIntermediatePropertyContext() {
+    Throwable root = new IOException("write failed");
+    Throwable inner = ExceptionMessages.wrap("Failed to write property: 
fs.s3a.endpoint", root);
+    RuntimeException outer = ExceptionMessages.wrap("Failed to create 
configuration", inner);
+
+    Assertions.assertTrue(
+        outer.getMessage().contains("fs.s3a.endpoint"),
+        "nested wrap must keep intermediate property context, got: " + 
outer.getMessage());
+    Assertions.assertEquals(
+        "Failed to create configuration: Failed to write property: 
fs.s3a.endpoint: write failed",
+        outer.getMessage());
+  }
+
+  @Test
+  public void testWithCauseAppendsUpstreamMessage() {
+    Throwable cause =
+        new IllegalArgumentException(
+            "Invalid value nonsense for configuration cleanup.policy: String 
must be one of:"
+                + " compact, delete");
+
+    String combined =
+        ExceptionMessages.withCause("Failed to alter topic properties for 
topic prop_probe", cause);
+
+    Assertions.assertEquals(
+        "Failed to alter topic properties for topic prop_probe: Invalid value 
nonsense for"
+            + " configuration cleanup.policy: String must be one of: compact, 
delete",
+        combined);
+  }
+
+  @Test
+  public void testWithCauseDoesNotDuplicateMessage() {
+    String context = "Failed to alter topic: bad value";
+    Throwable cause = new IllegalArgumentException("bad value");
+
+    Assertions.assertEquals(context, ExceptionMessages.withCause(context, 
cause));
+  }
+
+  @Test
+  public void testWrapPreservesCauseAndMessage() {
+    Throwable cause = new IllegalStateException("glue denied");
+    RuntimeException wrapped = ExceptionMessages.wrap("Glue error: schema 
drop_me", cause);
+
+    Assertions.assertEquals("Glue error: schema drop_me: glue denied", 
wrapped.getMessage());
+    Assertions.assertSame(cause, wrapped.getCause());
+  }
+
+  @Test
+  public void testIllegalArgumentPreservesCauseAndMessage() {
+    Throwable cause = new IllegalArgumentException("not allowed");
+    IllegalArgumentException wrapped =
+        ExceptionMessages.illegalArgument("Invalid properties for topic t1", 
cause);
+
+    Assertions.assertEquals("Invalid properties for topic t1: not allowed", 
wrapped.getMessage());
+    Assertions.assertSame(cause, wrapped.getCause());
+  }
+}

Reply via email to