{"record":{"id":"93ac563cbb21b375","repo":"pentaho/pentaho-kettle","slug":"jobhopmeta-exception-unabletosavehopinforep","errorCode":null,"errorMessage":"JobHopMeta.Exception.UnableToSaveHopInfoRep","messagePattern":"JobHopMeta\\.Exception\\.UnableToSaveHopInfoRep","errorType":"exception","errorClass":"KettleException","httpStatus":null,"severity":"error","filePath":"engine/src/main/java/org/pentaho/di/repository/kdr/delegates/KettleDatabaseRepositoryJobDelegate.java","lineNumber":673,"sourceCode":"        + id_job_hop ), dbe );\n\n    }\n  }\n\n  public void saveJobHopMeta( JobHopMeta hop, ObjectId id_job ) throws KettleException {\n    try {\n      ObjectId id_jobentry_from = null;\n      ObjectId id_jobentry_to = null;\n\n      id_jobentry_from = hop.getFromEntry() == null ? null : hop.getFromEntry().getObjectId();\n      id_jobentry_to = hop.getToEntry() == null ? null : hop.getToEntry().getObjectId();\n\n      // Insert new job hop in repository\n      //\n      hop.setObjectId( insertJobHop( id_job, id_jobentry_from, id_jobentry_to, hop.isEnabled(), hop\n        .getEvaluation(), hop.isUnconditional() ) );\n    } catch ( KettleDatabaseException dbe ) {\n      throw new KettleException( BaseMessages.getString( PKG, \"JobHopMeta.Exception.UnableToSaveHopInfoRep\", \"\"\n        + id_job ), dbe );\n\n    }\n  }\n\n  /**\n   * Read the database connections in the repository and add them to this job if they are not yet present.\n   *\n   * @param jobMeta\n   *          the job to put the database connections in\n   * @throws KettleException\n   */\n  public void readDatabases( JobMeta jobMeta ) throws KettleException {\n    readDatabases( jobMeta, true );\n  }\n\n  /**\n   * Read the database connections in the repository and add them to this job if they are not yet present.","sourceCodeStart":655,"sourceCodeEnd":691,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/engine/src/main/java/org/pentaho/di/repository/kdr/delegates/KettleDatabaseRepositoryJobDelegate.java#L655-L691","documentation":"KettleDatabaseRepositoryJobDelegate.saveJobHopMeta wraps a KettleDatabaseException thrown while inserting a job hop row into the Kettle database repository tables (R_JOB_HOP). The wrapper includes the internal job id for debugging and chains the underlying database exception as the cause. It means the hop could not be persisted during job save.","triggerScenarios":"Calling repository.save(job, ...) (or KettleDatabaseRepository.saveJob) when insertJobHop fails: e.g. the referenced job entries (id_jobentry_from/to) were not yet saved (null/invalid object ids), the repository table R_JOB_HOP is missing/corrupt, a database constraint violation occurs (duplicate hop, NULL in required column), or the repository connection was lost.","commonSituations":"Saving a job in Spoon against a database repository whose schema was not upgraded to the current Pentaho/Kettle version; network/DB outage mid-save; a job hop pointing to a job entry that failed to save; duplicate or self-referencing hop entries violating constraints.","solutions":["Check the chained KettleDatabaseException cause for the real database error (constraint, missing table, connection).","Verify the repository schema version matches your Kettle/Pentaho version and run the repository upgrade/repair if needed.","Ensure all job entries save successfully before the hop (fix earlier save errors on the job entries).","Test repository connectivity (db connection, credentials, network) and retry the save after restoring the connection."],"exampleFix":"// before\nrepository.save(job, versionComment, null, null); // fails mid-way with hop save error\n// after\ntry {\n  repository.save(job, versionComment, null, null);\n} catch (KettleException e) {\n  logError(\"Job save failed: \" + e.getCause(), e); // inspect KettleDatabaseException cause\n  // repair repository connection/schema, then retry\n}","handlingStrategy":"try-catch","validationCode":"if (!repository.isConnected()) throw new IllegalStateException(\"Repository not connected\");\nif (hop.getObjectId() == null) { /* ensure from/to job entries already saved with valid ids */ }\n// also verify repository schema version:\n// repository.getRepositoryMeta().getRepositoryVersion() vs expected Kettle version","typeGuard":null,"tryCatchPattern":"try {\n  repository.save(job, versionComment, null, null);\n} catch (KettleException e) {\n  Throwable cause = e.getCause();\n  if (cause instanceof KettleDatabaseException) {\n    logError(\"Hop persistence failed: \" + cause.getMessage(), cause);\n  }\n  // restore connection / fix schema, then retry\n}","preventionTips":["Always save job entries before saving hops so their object ids are valid.","Keep repository schema upgraded to match your Kettle version.","Monitor repository DB connectivity before batch saves.","Log and inspect the chained cause on every repository save failure."],"tags":["database","repository","persistence","job-hop"],"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"}