{"record":{"id":"d0a9289e386379fb","repo":"pentaho/pentaho-kettle","slug":"runtimeexception-wrapping-clone-failure-no-message-cause-is","errorCode":null,"errorMessage":"RuntimeException wrapping clone failure (no message; cause is the KettleException from RowMeta copy)","messagePattern":"RuntimeException wrapping clone failure \\(no message; cause is the KettleException from RowMeta copy\\)","errorType":"exception","errorClass":"java.lang.RuntimeException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/pentaho/di/core/row/RowMeta.java","lineNumber":89,"sourceCode":"        .cloneValueMeta( valueMetaInterface, targetType == null ? valueMetaInterface.getType() : targetType ) );\n    }\n    this.needRealClone = rowMeta.needRealClone;\n  }\n\n  private RowMeta( List<ValueMetaInterface> valueMetaList, RowMetaCache rowMetaCache ) {\n    lock = new ReentrantReadWriteLock();\n    this.cache = rowMetaCache;\n    this.valueMetaList = valueMetaList;\n    this.needRealClone = new ArrayList<>();\n  }\n\n  @Override\n  public RowMeta clone() {\n    lock.readLock().lock();\n    try {\n      return new RowMeta( this, null );\n    } catch ( Exception e ) {\n      throw new RuntimeException( e );\n    } finally {\n      lock.readLock().unlock();\n    }\n  }\n\n  /**\n   * This method copies the row metadata and sets all values to the specified type (usually String)\n   *\n   * @param targetType The target type\n   * @return The cloned metadata\n   * @throws if the target type could not be loaded from the plugin registry\n   */\n  @Override\n  public RowMetaInterface cloneToType( int targetType ) throws KettleValueException {\n    lock.readLock().lock();\n    try {\n      return new RowMeta( this, targetType );\n    } catch ( KettlePluginException e ) {","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/core/src/main/java/org/pentaho/di/core/row/RowMeta.java#L71-L107","documentation":"RowMeta.clone() copies the row metadata via the copy constructor new RowMeta(this, null) under the read lock; if that copy throws any KettleException (e.g. a cloned ValueMeta fails), it is rethrown as an unmodifiable RuntimeException with no message. The meaningful cause is the wrapped KettleException from RowMeta's copy logic.","triggerScenarios":"Calling rowMeta.clone() (often via row.clone() in steps) when the underlying value meta list contains a ValueMeta whose clone/copy fails - e.g. corrupt or exotic value metadata, or an internal copy error inside the RowMeta(ValueMetaInterface...) constructor.","commonSituations":"Cloning row metadata in long-running transformations where a custom/patched value meta implementation misbehaves; concurrency issues if metadata was mutated without the lock; corrupted serialized metadata deserialized from a repository/ktr.","solutions":["Unwrap and inspect getCause() - the KettleException (and its cause) names the failing value meta","Identify the offending field's ValueMetaInterface and rebuild/normalize it before cloning","Call cloneIntersection or copy via new RowMeta(original) explicitly to localize the failure","Update PDI to a version fixing the specific ValueMeta clone bug if the cause points to a known valueMeta"],"exampleFix":"// before\nRowMeta copy = (RowMeta) inputRowMeta.clone(); // bare RuntimeException, no message\n// after\ntry {\n  RowMeta copy = (RowMeta) inputRowMeta.clone();\n} catch ( RuntimeException e ) {\n  throw new KettleException( \"Failed to clone row metadata\", e.getCause() ); // preserve real cause\n}","handlingStrategy":"try-catch","validationCode":"if ( rowMeta == null || rowMeta.isEmpty() ) {\n  throw new IllegalStateException(\"no row metadata to clone\");\n}","typeGuard":"static boolean isCloneSafe(RowMeta rm) {\n  return rm != null && rm.size() > 0\n    && java.util.Arrays.stream(rm.getValueMetaList()).allMatch(java.util.Objects::nonNull);\n}","tryCatchPattern":"try {\n  RowMeta copy = (RowMeta) rowMeta.clone();\n} catch ( RuntimeException e ) {\n  // no message on purpose: the KettleException is the cause\n  throw new KettleException(\"RowMeta clone failed\", e.getCause());\n}","preventionTips":["Always unwrap getCause() - the RuntimeException itself carries no message","Mutate RowMeta only through thread-safe accessors; never share mutable ValueMeta across threads","Validate value metadata after deserializing from repository or ktr files","Keep PDI patched - several ValueMeta clone bugs were fixed in later versions"],"tags":["java","row-metadata","clone","concurrency"],"backgroundTag":"internal-invariant-violation","analyzedSha":"f3058517a153da500bf4551f46d79b91bf8ec552","analyzedAt":"2026-09-13T14:04:16.340Z","contentChangedAt":"2026-09-13T14:04:16.340Z","schemaVersion":2},"datasetVersion":"2026-09-20T23:17:15.980Z"}