pentaho/pentaho-kettle · error · FTPException
Please specify Local directory
Error message
Please specify Local directory
What it means
During FTP Put execution, the entry substitutes variables in the local directory setting and throws FTPException 'Please specify Local directory' when the resolved value is null. The local directory is mandatory — the entry must know where to read the files it will upload.
Solutions
- Set the Local directory field in the FTP Put job entry to a valid absolute path.
- If using a variable, confirm it is defined (via parameter, kettle.properties, or Set Variables) and resolves at runtime.
- Enable variable preview/logging to check the environmentSubstitute result before execution.
- Set the directory to use a file:/// URI if it references a virtual path so it resolves via URI.getPath().
Example fix
// before
localDirectory = "${MY_LOCAL_DIR}"; // variable not defined -> null
// after
localDirectory = "/opt/etl/outgoing"; // or define MY_LOCAL_DIR in kettle.properties Defensive patterns
Strategy: validation
Validate before calling
String resolved = entry.environmentSubstitute(entry.getLocalDirectory()); if ( resolved == null || resolved.trim().isEmpty() ) { throw new IllegalArgumentException("Local directory must be set on the FTP Put entry"); } Try / catch
try { result = runFtpPut(); } catch (FTPException e) { if ( e.getMessage().contains("Local directory") ) { failJob("Local directory not configured"); } else { throw e; } } Prevention
- Always fill the Local directory field with an absolute path.
- Audit variables used in entry fields exist in kettle.properties or job parameters.
- Dry-run jobs in the scheduler environment to catch unresolved variables.
When it happens
Trigger: Executing the FTP Put job entry with an empty/unset localDirectory field, or with a variable in the field (e.g. ${Internal.Job.Filename.Directory}) that resolves to null at runtime.
Common situations: Variable misspelled or not defined in the run environment; local directory field left blank when the job was cloned; running the job from a different environment (scheduler) where the variable isn't set.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- ChangeFileEncoding.Exception.TargetEncodingEmpty
- Endless loop detected for substitution of variable:
- FileSystemConfigBuilder could not parse parameter:
- JobEntryFTP.SocksProxy.PortMissingException
- JobEntryFTPDelete.SocksProxy.IncompleteCredentials
AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13).
Data as JSON: /api/errors/e0ee33719fab3902.
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:657
if ( log.isDetailed() ) {
logDetailed( BaseMessages.getString( PKG, "JobFTPPUT.Log.Logged", realUsername ) );
}
// Fix for PDI-2534 - add auxiliary FTP File List parsers to the ftpclient object.
this.hookInOtherParsers( ftpclient );
// move to spool dir ...
String realRemoteDirectory = environmentSubstitute( remoteDirectory );
if ( !Utils.isEmpty( realRemoteDirectory ) ) {
ftpclient.chdir( realRemoteDirectory );
if ( log.isDetailed() ) {
logDetailed( BaseMessages.getString( PKG, "JobFTPPUT.Log.ChangedDirectory", realRemoteDirectory ) );
}
}
String realLocalDirectory = environmentSubstitute( localDirectory );
if ( realLocalDirectory == null ) {
throw new FTPException( BaseMessages.getString( PKG, "JobFTPPUT.LocalDir.NotSpecified" ) );
} else {
// handle file:/// prefix
if ( realLocalDirectory.startsWith( "file:" ) ) {
realLocalDirectory = new URI( realLocalDirectory ).getPath();
}
}
final List<String> files;
File localFiles = new File( realLocalDirectory );
File[] children = localFiles.listFiles();
if ( children == null ) {
// Unable to read local directory for some reason...
if ( log.isDebug() ) {
logDebug(
BaseMessages.getString( PKG, "JobFTPPUT.Log.UnableToReadLocalDirectoryExtended", realLocalDirectory,
Files.isReadable( localFiles.toPath() ) ) );
} else if ( log.isBasic() ) {
logBasic(View on GitHub (pinned to f3058517a1)