{"record":{"id":"10d811e4b3e0c67a","repo":"pentaho/pentaho-kettle","slug":"transhopmeta-exception-unabletosavetransformationhopinfo","errorCode":null,"errorMessage":"TransHopMeta.Exception.UnableToSaveTransformationHopInfo","messagePattern":"TransHopMeta\\.Exception\\.UnableToSaveTransformationHopInfo","errorType":"exception","errorClass":"KettleException","httpStatus":null,"severity":"error","filePath":"engine/src/main/java/org/pentaho/di/repository/kdr/delegates/KettleDatabaseRepositoryTransDelegate.java","lineNumber":1103,"sourceCode":"    } catch ( KettleException dbe ) {\n      throw new KettleException( BaseMessages.getString(\n        PKG, \"TransDependency.Exception.UnableToSaveTransformationDepency\" )\n        + id_transformation, dbe );\n    }\n  }\n\n  public void saveTransHopMeta( TransHopMeta transHopMeta, ObjectId id_transformation ) throws KettleException {\n    try {\n      // See if a transformation hop with the same fromstep and tostep is\n      // already available...\n      ObjectId id_step_from = transHopMeta.getFromStep() == null ? null : transHopMeta.getFromStep().getObjectId();\n      ObjectId id_step_to = transHopMeta.getToStep() == null ? null : transHopMeta.getToStep().getObjectId();\n\n      // Insert new transMeta hop in repository\n      transHopMeta.setObjectId( insertTransHop( id_transformation, id_step_from, id_step_to, transHopMeta\n        .isEnabled() ) );\n    } catch ( KettleDatabaseException dbe ) {\n      throw new KettleException( BaseMessages.getString(\n        PKG, \"TransHopMeta.Exception.UnableToSaveTransformationHopInfo\" )\n        + id_transformation, dbe );\n    }\n  }\n\n  public TransHopMeta loadTransHopMeta( ObjectId id_trans_hop, List<StepMeta> steps ) throws KettleException {\n    TransHopMeta hopTransMeta = new TransHopMeta();\n    try {\n      hopTransMeta.setObjectId( id_trans_hop );\n\n      RowMetaAndData r = getTransHop( id_trans_hop );\n\n      hopTransMeta.setEnabled( r.getBoolean( \"ENABLED\", false ) );\n\n      long id_step_from = r.getInteger( \"ID_STEP_FROM\", 0 );\n      long id_step_to = r.getInteger( \"ID_STEP_TO\", 0 );\n\n      StepMeta fromStep = StepMeta.findStep( steps, new LongObjectId( id_step_from ) );","sourceCodeStart":1085,"sourceCodeEnd":1121,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/engine/src/main/java/org/pentaho/di/repository/kdr/delegates/KettleDatabaseRepositoryTransDelegate.java#L1085-L1121","documentation":"Thrown by saveTransHopMeta() when inserting a transformation hop (the link between two steps) into the repository fails with a KettleDatabaseException. The transformation ObjectId is appended to the message. Failures in insertTransHop() (FK violations, SQL errors) are wrapped here.","triggerScenarios":"Saving a transformation whose hops reference steps with null ObjectIds (steps not yet saved), or where id_transformation does not exist in R_TRANSFORMATION, causing insertTransHop() to fail.","commonSituations":"Ordering bug when saving a transformation manually step-by-step before the steps themselves, FK constraint on R_TRANS_HOP pointing at missing R_TRANSFORMATION/R_STEP rows, or DB connection loss during save.","solutions":["Save all StepMeta objects first so their ObjectIds are set before saving hops","Verify id_transformation exists (save the transformation header before hops)","Inspect the chained KettleDatabaseException for the SQL/FK error","Ensure the DB user has INSERT rights on R_TRANS_HOP"],"exampleFix":"// before\nTransHopMeta hop = new TransHopMeta(stepA, stepB, true);\ndelegate.saveTransHopMeta(hop, transId); // steps not saved yet\n\n// after\ndelegate.saveTransformation(transMeta); // persists steps, sets ObjectIds\nTransHopMeta hop = new TransHopMeta(stepA, stepB, true);\ndelegate.saveTransHopMeta(hop, transId);","handlingStrategy":"try-catch","validationCode":"// Java: steps must be persisted before hops\nboolean stepsSaved = transHopMeta.getFromStep().getObjectId() != null\n    && transHopMeta.getToStep().getObjectId() != null\n    && idTransformation != null;\nif (!stepsSaved) {\n  throw new IllegalStateException(\"Save transformation and steps before hops\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  delegate.saveTransHopMeta(hop, transId);\n} catch (KettleException e) {\n  log.error(\"Hop save failed for transformation \" + transId + \": \" + e.getCause(), e);\n  throw e; // roll back partial save\n}","preventionTips":["Save the transformation header, then steps, then hops — in that order","Never construct hops from steps lacking ObjectIds when persisting manually","Check FK errors in the cause chain immediately","Prefer rep.save(transMeta, ...) which handles ordering internally"],"tags":["database","repository","kettle","hop","insert"],"backgroundTag":"database-write-failed","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"}