pentaho/pentaho-kettle · error · KettleXMLException

Unable to load job entry of type 'FTPPUT' from XML node

Error message

Unable to load job entry of type 'FTPPUT' from XML node

What it means

JobEntryFTPPUT.loadXML wraps any KettleXMLException raised while parsing the job entry's XML node and rethrows it as 'Unable to load job entry of type FTPPUT from XML node'. This means the FTP Put job entry could not be deserialized from the .kjb XML document.

Solutions

  1. Open the .kjb file in a text editor and verify the FTPPUT entry XML block is well-formed with all expected tags.
  2. Check the chained cause (xe) in the log to identify the exact XML parse failure.
  3. Recreate the FTP Put job entry in Spoon and re-save the job to regenerate valid XML.
  4. Verify the file was not produced by an incompatible Kettle version; re-export from the original tool if needed.
Defensive patterns

Strategy: validation

Validate before calling

Document doc = XMLHandler.loadXMLFile(xmlInputStream); Node entry = XMLHandler.getSubNode(doc, "entry"); if (entry == null) throw new IllegalArgumentException("Missing <entry> node for FTPPUT");

Try / catch

try { entry.loadXML(node, databases, slaveServers, rep, metaStore); } catch (KettleXMLException e) { log.error("FTPPUT XML load failed: " + e.getCause(), e); }

Prevention

When it happens

Trigger: Calling loadXML with a malformed or missing FTPPUT XML node — e.g. XMLHandler.getTagValue hitting an unexpected structure, a corrupted <entry> block, or an XML node for a different entry type passed to this loader.

Common situations: Hand-edited or truncated .kjb files; .kjb files from newer/older Pentaho versions with renamed tags; copy-paste corruption of job XML; loading a job saved by a different tool.

Related errors


AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13). Data as JSON: /api/errors/267b6ed5b2450f6d. Report an issue: GitHub.

Appendix: source

Thrown at plugins/put-a-file-with-ftp/impl/src/main/java/org/pentaho/di/job/entries/ftpput/JobEntryFTPPUT.java:235

      proxyHost = XMLHandler.getTagValue( entrynode, XML_TAG_PROXY_HOST );
      proxyPort = XMLHandler.getTagValue( entrynode, XML_TAG_PROXY_PORT );
      proxyUsername = XMLHandler.getTagValue( entrynode, XML_TAG_PROXY_USERNAME );
      proxyPassword =
        Encr.decryptPasswordOptionallyEncrypted( XMLHandler.getTagValue( entrynode, XML_TAG_PROXY_PASSWORD ) );
      socksProxyHost = XMLHandler.getTagValue( entrynode, XML_TAG_SOCKSPROXY_HOST );
      socksProxyPort = XMLHandler.getTagValue( entrynode, XML_TAG_SOCKSPROXY_PORT );
      socksProxyUsername = XMLHandler.getTagValue( entrynode, XML_TAG_SOCKSPROXY_USERNAME );
      socksProxyPassword =
        Encr.decryptPasswordOptionallyEncrypted( XMLHandler.getTagValue( entrynode, XML_TAG_SOCKSPROXY_PASSWORD ) );

      if ( controlEncoding == null ) {
        // if we couldn't retrieve an encoding, assume it's an old instance and
        // put in the the encoding used before v 2.4.0
        controlEncoding = LEGACY_CONTROL_ENCODING;
      }
    } catch ( KettleXMLException xe ) {
      throw new KettleXMLException( BaseMessages.getString( PKG, "JobFTPPUT.Log.UnableToLoadFromXml" ), xe );
    }
  }

  @Override
  public void loadRep( Repository rep, IMetaStore metaStore, ObjectId jobEntryId, List<DatabaseMeta> databases,
                       List<SlaveServer> slaveServers ) throws KettleException {
    try {
      serverName = rep.getJobEntryAttributeString( jobEntryId, XML_TAG_SERVERNAME );
      serverPort = rep.getJobEntryAttributeString( jobEntryId, XML_TAG_SERVERPORT );
      userName = rep.getJobEntryAttributeString( jobEntryId, XML_TAG_USERNAME );
      password =
        Encr.decryptPasswordOptionallyEncrypted( rep.getJobEntryAttributeString( jobEntryId, XML_TAG_PASSWORD ) );
      remoteDirectory = rep.getJobEntryAttributeString( jobEntryId, XML_TAG_REMOTE_DIRECTORY );
      localDirectory = rep.getJobEntryAttributeString( jobEntryId, XML_TAG_LOCAL_DIRECTORY );
      wildcard = rep.getJobEntryAttributeString( jobEntryId, XML_TAG_WILDCARD );
      binaryMode = rep.getJobEntryAttributeBoolean( jobEntryId, XML_TAG_BINARY );
      timeout = rep.getJobEntryAttributeString( jobEntryId, XML_TAG_TIMEOUT );
      remove = rep.getJobEntryAttributeBoolean( jobEntryId, XML_TAG_REMOVE );

View on GitHub (pinned to f3058517a1)