You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by lo...@apache.org on 2022/12/16 13:13:47 UTC

[nifi-minifi-cpp] branch main updated (62061a136 -> 5378719e5)

This is an automated email from the ASF dual-hosted git repository.

lordgamez pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git


    from 62061a136 MINIFICPP-2017 - Use std::string_view in join
     new f4611e542 MINIFICPP-1917 - Add json schema generation
     new e8906515e MINIFICPP-1862 use std::filesystem::path instead of std::string where appropriate
     new 5378719e5 MINIFICPP-2014 Add ProcessSession::remove to Lua API

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 CMakeLists.txt                                     |   2 +
 CMakeSettings.json                                 |   4 -
 Windows.md                                         |   2 +-
 cmake/BuildTests.cmake                             |   7 -
 .../FindBZip2.cmake => Findnlohmann_json.cmake}    |  26 +-
 cmake/GoogleCloudCpp.cmake                         |   2 -
 ...xpectedLite.cmake => JsonSchemaValidator.cmake} |  14 +-
 cmake/Nlohmann.cmake                               |  24 --
 docker/Dockerfile                                  |   2 +-
 docker/bionic/Dockerfile                           |   2 +-
 docker/centos/Dockerfile                           |   2 +-
 docker/fedora/Dockerfile                           |   2 +-
 docker/focal/Dockerfile                            |   2 +-
 encrypt-config/EncryptConfig.cpp                   |  28 +-
 encrypt-config/EncryptConfig.h                     |   7 +-
 encrypt-config/tests/ConfigFileTests.cpp           |   4 +-
 extensions/aws/processors/FetchS3Object.cpp        |   8 +-
 extensions/aws/s3/S3Wrapper.cpp                    |   5 +-
 extensions/aws/s3/S3Wrapper.h                      |  18 +-
 .../azure/processors/ListAzureDataLakeStorage.cpp  |   4 +-
 extensions/azure/storage/AzureDataLakeStorage.cpp  |   9 +-
 extensions/azure/storage/AzureDataLakeStorage.h    |   4 +-
 extensions/civetweb/tests/ListenHTTPTests.cpp      |  51 ++-
 extensions/coap/tests/CoapIntegrationBase.h        |   4 +-
 .../tests/ExpressionLanguageTests.cpp              |  21 +-
 extensions/http-curl/client/HTTPClient.cpp         |   6 +-
 .../tests/C2ClearCoreComponentStateTest.cpp        |  16 +-
 extensions/http-curl/tests/C2ConfigEncryption.cpp  |   4 +-
 .../tests/C2DescribeCoreComponentStateTest.cpp     |  14 +-
 .../http-curl/tests/C2FetchFlowIfMissingTest.cpp   |   8 +-
 extensions/http-curl/tests/C2MetricsTest.cpp       |   5 +-
 extensions/http-curl/tests/C2NullConfiguration.cpp |   7 +-
 extensions/http-curl/tests/C2UpdateAssetTest.cpp   |   2 +-
 .../http-curl/tests/C2VerifyServeResults.cpp       |  12 +-
 .../tests/ControllerServiceIntegrationTests.cpp    |   2 +-
 extensions/http-curl/tests/HTTPSiteToSiteTests.cpp |   6 +-
 .../http-curl/tests/HttpPostIntegrationTest.cpp    |  14 +-
 extensions/http-curl/tests/SiteToSiteRestTest.cpp  |  14 +-
 .../http-curl/tests/TimeoutHTTPSiteToSiteTests.cpp |   6 +-
 extensions/http-curl/tests/VerifyInvokeHTTP.h      |   6 +-
 .../http-curl/tests/VerifyInvokeHTTPPostTest.cpp   |   2 +-
 extensions/jni/JVMCreator.h                        |   2 +-
 extensions/jni/jvm/JVMLoader.h                     |  18 +-
 extensions/jni/jvm/JavaControllerService.h         |   4 +-
 extensions/jni/jvm/JniReferenceObjects.h           | 106 ++---
 extensions/libarchive/ArchiveMetadata.h            |   2 +-
 extensions/libarchive/ArchiveTests.h               |   9 +-
 extensions/libarchive/FocusArchiveEntry.cpp        |   8 +-
 extensions/libarchive/ManipulateArchive.cpp        |  18 +-
 extensions/libarchive/UnfocusArchiveEntry.cpp      |  16 +-
 extensions/librdkafka/KafkaProcessorBase.cpp       |  12 +-
 extensions/librdkafka/PublishKafka.cpp             |  12 +-
 extensions/pcap/CapturePacket.cpp                  |  10 +-
 extensions/pcap/CapturePacket.h                    |  22 +-
 .../pdh/tests/PerformanceDataMonitorTests.cpp      |  20 +-
 .../rocksdb-repos/DatabaseContentRepository.cpp    |   2 +-
 extensions/rocksdb-repos/FlowFileRepository.cpp    |  14 +-
 extensions/rocksdb-repos/FlowFileRepository.h      |   6 +-
 extensions/script/ExecuteScript.cpp                |   5 +-
 extensions/script/ScriptEngine.h                   |   5 +-
 extensions/script/lua/LuaProcessSession.cpp        |  14 +
 extensions/script/lua/LuaProcessSession.h          |   1 +
 extensions/script/lua/LuaScriptEngine.cpp          |   7 +-
 extensions/script/lua/LuaScriptEngine.h            |   6 +-
 .../script/python/ExecutePythonProcessor.cpp       |   3 +-
 extensions/script/python/PythonCreator.h           |  34 +-
 extensions/script/python/PythonScriptEngine.cpp    |   9 +-
 .../script/tests/ExecutePythonProcessorTests.cpp   |  63 ++-
 .../TestExecuteScriptProcessorWithLuaScript.cpp    |  71 ++--
 .../TestExecuteScriptProcessorWithPythonScript.cpp | 305 +++++++-------
 extensions/sftp/processors/FetchSFTP.cpp           |  39 +-
 extensions/sftp/processors/ListSFTP.cpp            |  36 +-
 extensions/sftp/processors/ListSFTP.h              |   6 +-
 extensions/sftp/processors/PutSFTP.cpp             |  63 ++-
 extensions/sftp/tests/CMakeLists.txt               |   2 +-
 extensions/sftp/tests/FetchSFTPTests.cpp           |  84 +---
 extensions/sftp/tests/ListSFTPTests.cpp            |  61 +--
 extensions/sftp/tests/ListThenFetchSFTPTests.cpp   |  66 +--
 extensions/sftp/tests/PutSFTPTests.cpp             | 121 +++---
 extensions/sftp/tests/tools/SFTPTestServer.cpp     |  22 +-
 extensions/sftp/tests/tools/SFTPTestServer.h       |  16 +-
 .../processors/ExecuteProcess.cpp                  |   8 +-
 .../processors/ExecuteProcess.h                    |   2 +-
 .../standard-processors/processors/FetchFile.cpp   |  49 ++-
 .../standard-processors/processors/FetchFile.h     |  16 +-
 .../standard-processors/processors/GetFile.cpp     |  57 ++-
 .../standard-processors/processors/GetFile.h       |  10 +-
 .../standard-processors/processors/ListFile.cpp    |  49 ++-
 .../standard-processors/processors/ListFile.h      |  12 +-
 .../standard-processors/processors/PutFile.cpp     | 101 ++---
 .../standard-processors/processors/PutFile.h       |  14 +-
 .../standard-processors/processors/PutTCP.cpp      |   6 +-
 .../standard-processors/processors/TailFile.cpp    | 168 ++++----
 .../standard-processors/processors/TailFile.h      |  35 +-
 .../tests/integration/SecureSocketGetTCPTest.cpp   |   6 +-
 .../tests/integration/TailFileTest.cpp             |  19 +-
 .../tests/unit/AttributesToJSONTests.cpp           |  22 +-
 .../tests/unit/ExecuteProcessTests.cpp             |  12 +-
 .../tests/unit/ExtractTextTests.cpp                |  16 +-
 .../tests/unit/FetchFileTests.cpp                  |  83 ++--
 .../tests/unit/GenerateFlowFileTests.cpp           |  42 +-
 .../tests/unit/GetFileTests.cpp                    |  30 +-
 .../tests/unit/HashContentTest.cpp                 |  12 +-
 .../tests/unit/ListFileTests.cpp                   |  61 +--
 .../tests/unit/ListenSyslogTests.cpp               |  13 +-
 .../tests/unit/ListenTcpTests.cpp                  |  25 +-
 .../tests/unit/ProcessorTests.cpp                  |  60 ++-
 .../tests/unit/PutFileTests.cpp                    | 202 +++++----
 .../tests/unit/RetryFlowFileTests.cpp              |  24 +-
 .../tests/unit/TailFileTests.cpp                   | 293 ++++++-------
 extensions/windows-event-log/Bookmark.cpp          |  12 +-
 extensions/windows-event-log/Bookmark.h            |   4 +-
 extensions/windows-event-log/tests/CWELTestUtils.h |   4 +-
 libminifi/CMakeLists.txt                           |   2 +-
 libminifi/include/Defaults.h                       |  18 +-
 .../log.c => libminifi/include/agent/JsonSchema.h  |  11 +-
 libminifi/include/controllers/SSLContextService.h  |  24 +-
 libminifi/include/core/ConfigurableComponent.h     |  12 +
 libminifi/include/core/ContentRepository.h         |  17 +-
 libminifi/include/core/FlowConfiguration.h         |  28 +-
 .../include/core/ProcessSessionReadCallback.h      |  10 +-
 libminifi/include/core/PropertyValue.h             |   4 +
 libminifi/include/core/extension/Utils.h           |   8 +-
 .../include/core/logging/LoggerConfiguration.h     |  19 +-
 libminifi/include/core/yaml/YamlConfiguration.h    |   2 +-
 libminifi/include/io/FileStream.h                  |  27 +-
 libminifi/include/properties/Decryptor.h           |   2 +-
 libminifi/include/properties/Properties.h          |  28 +-
 libminifi/include/properties/PropertiesFile.h      |  13 +-
 libminifi/include/utils/ChecksumCalculator.h       |  23 +-
 libminifi/include/utils/Environment.h              |  29 +-
 libminifi/include/utils/FileReaderCallback.h       |   5 +-
 libminifi/include/utils/TestUtils.h                |  18 +-
 libminifi/include/utils/crypto/EncryptionManager.h |   5 +-
 .../include/utils/crypto/EncryptionProvider.h      |   3 +-
 libminifi/include/utils/file/FileManager.h         |  70 +--
 libminifi/include/utils/file/FilePattern.h         |  26 +-
 libminifi/include/utils/file/FileSystem.h          |   4 +-
 libminifi/include/utils/file/FileUtils.h           | 337 ++++-----------
 libminifi/include/utils/file/PathUtils.h           |  45 +-
 libminifi/include/utils/net/Ssl.h                  |   6 +-
 libminifi/include/utils/tls/CertificateUtils.h     |   4 +-
 libminifi/src/agent/JsonSchema.cpp                 | 467 +++++++++++++++++++++
 libminifi/src/controllers/SSLContextService.cpp    | 124 +++---
 libminifi/src/core/FlowConfiguration.cpp           |  21 +-
 libminifi/src/core/ProcessSessionReadCallback.cpp  |  40 +-
 libminifi/src/core/logging/LoggerConfiguration.cpp |  30 +-
 .../src/core/repository/FileSystemRepository.cpp   |   8 +-
 .../core/state/nodes/ConfigurationChecksums.cpp    |   2 +-
 libminifi/src/core/yaml/YamlConfiguration.cpp      |   2 +-
 libminifi/src/io/FileStream.cpp                    |  26 +-
 libminifi/src/properties/Properties.cpp            |  36 +-
 libminifi/src/properties/PropertiesFile.cpp        |  14 +-
 libminifi/src/utils/ChecksumCalculator.cpp         |  24 +-
 libminifi/src/utils/Environment.cpp                |  77 +---
 libminifi/src/utils/FileReaderCallback.cpp         |  10 +-
 libminifi/src/utils/StringUtils.cpp                |   3 +-
 libminifi/src/utils/crypto/EncryptionManager.cpp   |  20 +-
 libminifi/src/utils/crypto/EncryptionProvider.cpp  |  17 +-
 libminifi/src/utils/file/FilePattern.cpp           |  52 +--
 libminifi/src/utils/file/FileSystem.cpp            |  26 +-
 libminifi/src/utils/file/FileUtils.cpp             |   9 +-
 libminifi/src/utils/file/PathUtils.cpp             |  49 ---
 libminifi/src/utils/net/SslServer.cpp              |   6 +-
 libminifi/src/utils/tls/CertificateUtils.cpp       |   8 +-
 libminifi/test/Path.h                              |  69 ---
 libminifi/test/TestBase.cpp                        |   6 +-
 libminifi/test/TestBase.h                          |  10 +-
 libminifi/test/Utils.h                             |  20 +-
 .../test/archive-tests/CompressContentTests.cpp    |  40 +-
 libminifi/test/archive-tests/FocusArchiveTests.cpp |  20 +-
 .../test/archive-tests/ManipulateArchiveTests.cpp  |  12 +-
 libminifi/test/archive-tests/MergeFileTests.cpp    |  14 +-
 libminifi/test/archive-tests/util/ArchiveTests.cpp |  12 +-
 libminifi/test/aws-tests/FetchS3ObjectTests.cpp    |  11 +-
 libminifi/test/aws-tests/S3TestsFixture.h          |   8 +-
 .../azure-tests/AzureBlobStorageTestsFixture.h     |  18 +-
 .../azure-tests/AzureDataLakeStorageTestsFixture.h |  16 +-
 libminifi/test/flow-tests/SessionTests.cpp         |   4 +-
 libminifi/test/integration/IntegrationBase.h       |  10 +-
 .../PersistableKeyValueStoreServiceTest.cpp        |   9 +-
 .../UnorderedMapKeyValueStoreServiceTest.cpp       |   8 +-
 .../test/persistence-tests/PersistenceTests.cpp    |   8 +-
 .../test/rocksdb-tests/ContentSessionTests.cpp     |   4 +-
 .../rocksdb-tests/DBContentRepositoryTests.cpp     |  20 +-
 .../rocksdb-tests/DBProvenanceRepositoryTests.cpp  |   8 +-
 libminifi/test/rocksdb-tests/EncryptionTests.cpp   |  20 +-
 libminifi/test/rocksdb-tests/RepoTests.cpp         |  32 +-
 .../test/rocksdb-tests/RocksDBStreamTests.cpp      |   2 +-
 libminifi/test/rocksdb-tests/RocksDBTests.cpp      |  13 +-
 libminifi/test/rocksdb-tests/SwapTests.cpp         |   4 +-
 .../{coap-tests => schema-tests}/CMakeLists.txt    |  26 +-
 libminifi/test/schema-tests/SchemaTests.cpp        | 258 ++++++++++++
 libminifi/test/sql-tests/SQLTestController.h       |   9 +-
 libminifi/test/unit/ChecksumCalculatorTests.cpp    |  28 +-
 .../test/unit/ConfigurationChecksumsTests.cpp      |  12 +-
 libminifi/test/unit/EnvironmentUtilsTests.cpp      |  59 +--
 libminifi/test/unit/FilePatternTests.cpp           |   8 +-
 libminifi/test/unit/FileStreamTests.cpp            |  67 +--
 libminifi/test/unit/FileSystemRepositoryTests.cpp  |   2 +-
 libminifi/test/unit/FileSystemTests.cpp            |  14 +-
 libminifi/test/unit/FileTriggerTests.cpp           |  16 +-
 libminifi/test/unit/FileUtilsTests.cpp             | 222 ++--------
 libminifi/test/unit/PathUtilsTests.cpp             |  18 -
 minifi_main/AgentDocs.cpp                          |  22 +-
 minifi_main/AgentDocs.h                            |   2 +-
 minifi_main/MainHelper.cpp                         |  73 ++--
 minifi_main/MainHelper.h                           |   3 +-
 minifi_main/MiNiFiMain.cpp                         |  47 ++-
 nanofi/tests/CAPITests.cpp                         |  21 +-
 run_clang_tidy.sh                                  |  10 +-
 .../nlohmann_lib_as_interface.patch                |  37 +-
 win_build_vs.bat                                   |   2 +-
 213 files changed, 3053 insertions(+), 3337 deletions(-)
 copy cmake/{bzip2/dummy/FindBZip2.cmake => Findnlohmann_json.cmake} (51%)
 copy cmake/{ExpectedLite.cmake => JsonSchemaValidator.cmake} (69%)
 delete mode 100644 cmake/Nlohmann.cmake
 copy nanofi/src/core/log.c => libminifi/include/agent/JsonSchema.h (83%)
 create mode 100644 libminifi/src/agent/JsonSchema.cpp
 delete mode 100644 libminifi/test/Path.h
 copy libminifi/test/{coap-tests => schema-tests}/CMakeLists.txt (52%)
 create mode 100644 libminifi/test/schema-tests/SchemaTests.cpp


[nifi-minifi-cpp] 03/03: MINIFICPP-2014 Add ProcessSession::remove to Lua API

Posted by lo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

lordgamez pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit 5378719e57cd2d1d5f12b25339e0b5cd4930dcac
Author: Martin Zink <ma...@apache.org>
AuthorDate: Tue Dec 13 12:25:03 2022 +0100

    MINIFICPP-2014 Add ProcessSession::remove to Lua API
    
    Signed-off-by: Gabor Gyimesi <ga...@gmail.com>
    
    This closes #1472
---
 extensions/script/lua/LuaProcessSession.cpp         | 14 ++++++++++++++
 extensions/script/lua/LuaProcessSession.h           |  1 +
 extensions/script/lua/LuaScriptEngine.cpp           |  3 ++-
 .../TestExecuteScriptProcessorWithLuaScript.cpp     | 21 +++++++++++++++++++++
 .../TestExecuteScriptProcessorWithPythonScript.cpp  | 20 ++++++++++++++++++++
 5 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/extensions/script/lua/LuaProcessSession.cpp b/extensions/script/lua/LuaProcessSession.cpp
index 44a683711..610aea69e 100644
--- a/extensions/script/lua/LuaProcessSession.cpp
+++ b/extensions/script/lua/LuaProcessSession.cpp
@@ -131,4 +131,18 @@ void LuaProcessSession::releaseCoreResources() {
   session_.reset();
 }
 
+void LuaProcessSession::remove(const std::shared_ptr<script::ScriptFlowFile>& script_flow_file) {
+  if (!session_) {
+    throw std::runtime_error("Access of ProcessSession after it has been released");
+  }
+
+  auto flow_file = script_flow_file->getFlowFile();
+
+  if (!flow_file) {
+    throw std::runtime_error("Access of FlowFile after it has been released");
+  }
+
+  session_->remove(flow_file);
+}
+
 }  // namespace org::apache::nifi::minifi::lua
diff --git a/extensions/script/lua/LuaProcessSession.h b/extensions/script/lua/LuaProcessSession.h
index c428239fb..a05b9e6cf 100644
--- a/extensions/script/lua/LuaProcessSession.h
+++ b/extensions/script/lua/LuaProcessSession.h
@@ -39,6 +39,7 @@ class LuaProcessSession {
   void transfer(const std::shared_ptr<script::ScriptFlowFile> &flow_file, const core::Relationship& relationship);
   void read(const std::shared_ptr<script::ScriptFlowFile> &script_flow_file, sol::table input_stream_callback);
   void write(const std::shared_ptr<script::ScriptFlowFile> &flow_file, sol::table output_stream_callback);
+  void remove(const std::shared_ptr<script::ScriptFlowFile>& flow_file);
 
   /**
    * Sometimes we want to release shared pointers to core resources when
diff --git a/extensions/script/lua/LuaScriptEngine.cpp b/extensions/script/lua/LuaScriptEngine.cpp
index bce738dfd..11b235b2b 100644
--- a/extensions/script/lua/LuaScriptEngine.cpp
+++ b/extensions/script/lua/LuaScriptEngine.cpp
@@ -46,7 +46,8 @@ LuaScriptEngine::LuaScriptEngine() {
       "get", &lua::LuaProcessSession::get,
       "read", &lua::LuaProcessSession::read,
       "write", &lua::LuaProcessSession::write,
-      "transfer", &lua::LuaProcessSession::transfer);
+      "transfer", &lua::LuaProcessSession::transfer,
+      "remove", &lua::LuaProcessSession::remove);
   lua_.new_usertype<script::ScriptFlowFile>(
       "FlowFile",
       "getAttribute", &script::ScriptFlowFile::getAttribute,
diff --git a/extensions/script/tests/TestExecuteScriptProcessorWithLuaScript.cpp b/extensions/script/tests/TestExecuteScriptProcessorWithLuaScript.cpp
index 5fbc5195a..71d8d3a1d 100644
--- a/extensions/script/tests/TestExecuteScriptProcessorWithLuaScript.cpp
+++ b/extensions/script/tests/TestExecuteScriptProcessorWithLuaScript.cpp
@@ -21,6 +21,7 @@
 #include <string>
 #include <set>
 
+#include "SingleProcessorTestController.h"
 #include "TestBase.h"
 #include "Catch.h"
 
@@ -31,6 +32,8 @@
 #include "utils/file/FileUtils.h"
 #include "utils/TestUtils.h"
 
+namespace org::apache::nifi::minifi::processors::test {
+
 TEST_CASE("Script engine is not set", "[executescriptMisconfiguration]") {
   TestController testController;
   auto plan = testController.createPlan();
@@ -454,3 +457,21 @@ TEST_CASE("Lua: Non existent script file should throw", "[executescriptLuaNonExi
 
   logTestController.reset();
 }
+
+TEST_CASE("Lua can remove flowfiles", "[ExecuteScript]") {
+  const auto execute_script = std::make_shared<ExecuteScript>("ExecuteScript");
+
+  minifi::test::SingleProcessorTestController controller{execute_script};
+  LogTestController::getInstance().setTrace<minifi::processors::ExecuteScript>();
+  execute_script->setProperty(ExecuteScript::ScriptEngine, "lua");
+  execute_script->setProperty(ExecuteScript::ScriptBody.getName(),
+      R"(
+        function onTrigger(context, session)
+          flow_file = session:get()
+          session:remove(flow_file)
+        end
+      )");
+  REQUIRE_NOTHROW(controller.trigger("hello"));
+}
+
+}  // namespace org::apache::nifi::minifi::processors::test
diff --git a/extensions/script/tests/TestExecuteScriptProcessorWithPythonScript.cpp b/extensions/script/tests/TestExecuteScriptProcessorWithPythonScript.cpp
index b03dd0844..a1d803765 100644
--- a/extensions/script/tests/TestExecuteScriptProcessorWithPythonScript.cpp
+++ b/extensions/script/tests/TestExecuteScriptProcessorWithPythonScript.cpp
@@ -20,6 +20,7 @@
 #include <string>
 #include <set>
 
+#include "SingleProcessorTestController.h"
 #include "TestBase.h"
 #include "Catch.h"
 
@@ -31,6 +32,8 @@
 #include "utils/file/PathUtils.h"
 #include "utils/TestUtils.h"
 
+namespace org::apache::nifi::minifi::processors::test {
+
 TEST_CASE("Script engine is not set", "[executescriptMisconfiguration]") {
   TestController test_controller;
   auto plan = test_controller.createPlan();
@@ -407,3 +410,20 @@ TEST_CASE("Python: Non existent script file should throw", "[executescriptPython
 
   log_test_controller.reset();
 }
+
+TEST_CASE("Python can remove flowfiles", "[ExecuteScript]") {
+  const auto execute_script = std::make_shared<ExecuteScript>("ExecuteScript");
+
+  minifi::test::SingleProcessorTestController controller{execute_script};
+  LogTestController::getInstance().setTrace<minifi::processors::ExecuteScript>();
+  execute_script->setProperty(ExecuteScript::ScriptEngine, "python");
+  execute_script->setProperty(ExecuteScript::ScriptBody.getName(),
+      R"(
+        def onTrigger(context, session):
+          flow_file = session.get()
+          session.remove(flow_file);
+      )");
+  REQUIRE_NOTHROW(controller.trigger("hello"));
+}
+
+}  // namespace org::apache::nifi::minifi::processors::test


[nifi-minifi-cpp] 01/03: MINIFICPP-1917 - Add json schema generation

Posted by lo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

lordgamez pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit f4611e542674f65d5a8d7d115bd528179261e67f
Author: Adam Debreceni <ad...@apache.org>
AuthorDate: Tue Aug 23 17:13:56 2022 +0200

    MINIFICPP-1917 - Add json schema generation
    
    Co-authored-by: Martin Zink <ma...@protonmail.com>
    Signed-off-by: Gabor Gyimesi <ga...@gmail.com>
    
    This closes #1413
---
 CMakeLists.txt                                     |   2 +
 cmake/{Nlohmann.cmake => Findnlohmann_json.cmake}  |  17 +-
 cmake/GoogleCloudCpp.cmake                         |   2 -
 .../{Nlohmann.cmake => JsonSchemaValidator.cmake}  |  16 +-
 libminifi/CMakeLists.txt                           |   2 +-
 libminifi/include/agent/JsonSchema.h               |  26 ++
 libminifi/include/core/PropertyValue.h             |   4 +
 libminifi/src/agent/JsonSchema.cpp                 | 467 +++++++++++++++++++++
 .../test/schema-tests/CMakeLists.txt               |  20 +-
 libminifi/test/schema-tests/SchemaTests.cpp        | 258 ++++++++++++
 minifi_main/MiNiFiMain.cpp                         |  27 +-
 .../nlohmann_lib_as_interface.patch                |  37 +-
 12 files changed, 847 insertions(+), 31 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index cb79627e9..b049ec65e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -610,6 +610,8 @@ registerTest("${TEST_DIR}/keyvalue-tests")
 
 registerTest("${TEST_DIR}/flow-tests")
 
+registerTest("${TEST_DIR}/schema-tests")
+
 if (NOT DISABLE_ROCKSDB AND NOT DISABLE_LIBARCHIVE)
     registerTest("${TEST_DIR}/persistence-tests")
 endif()
diff --git a/cmake/Nlohmann.cmake b/cmake/Findnlohmann_json.cmake
similarity index 51%
copy from cmake/Nlohmann.cmake
copy to cmake/Findnlohmann_json.cmake
index 9ad44c56c..e81fd9659 100644
--- a/cmake/Nlohmann.cmake
+++ b/cmake/Findnlohmann_json.cmake
@@ -17,8 +17,17 @@
 # under the License.
 #
 
-set(NLOHMANN_JSON_INCLUDE_DIR "${CMAKE_BINARY_DIR}/_deps/nlohmann/" CACHE STRING "" FORCE)
-if(NOT EXISTS "${NLOHMANN_JSON_INCLUDE_DIR}/nlohmann/json.hpp")
-    file(DOWNLOAD "https://github.com/nlohmann/json/releases/download/v3.10.5/json.hpp" "${NLOHMANN_JSON_INCLUDE_DIR}/nlohmann/json.hpp"
-            EXPECTED_HASH SHA256=e832d339d9e0c042e7dff807754769d778cf5d6ae9730ce21eed56de99cb5e86)
+if (NOT nlohmann_json_FOUND)
+    set(nlohmann_json_FOUND "YES" CACHE STRING "" FORCE)
+    set(nlohmann_json_INCLUDE_DIR "${CMAKE_BINARY_DIR}/_deps/nlohmann/" CACHE STRING "" FORCE)
+    if(NOT EXISTS "${nlohmann_json_INCLUDE_DIR}/nlohmann/json.hpp")
+        file(DOWNLOAD "https://github.com/nlohmann/json/releases/download/v3.10.5/json.hpp" "${nlohmann_json_INCLUDE_DIR}/nlohmann/json.hpp"
+                EXPECTED_HASH SHA256=e832d339d9e0c042e7dff807754769d778cf5d6ae9730ce21eed56de99cb5e86)
+    endif()
+endif()
+
+if(NOT TARGET nlohmann_json::nlohmann_json)
+    add_library(nlohmann_json::nlohmann_json INTERFACE IMPORTED)
+    set_target_properties(nlohmann_json::nlohmann_json PROPERTIES
+        INTERFACE_INCLUDE_DIRECTORIES "${nlohmann_json_INCLUDE_DIR}")
 endif()
diff --git a/cmake/GoogleCloudCpp.cmake b/cmake/GoogleCloudCpp.cmake
index 4aa57040b..71d08c4c2 100644
--- a/cmake/GoogleCloudCpp.cmake
+++ b/cmake/GoogleCloudCpp.cmake
@@ -17,10 +17,8 @@
 # under the License.
 #
 include(FetchContent)
-include(Nlohmann)
 include(Abseil)
 
-set(GOOGLE_CLOUD_CPP_NLOHMANN_JSON_HEADER ${NLOHMANN_JSON_INCLUDE_DIR})
 set(CRC32C_USE_GLOG OFF CACHE INTERNAL crc32c-glog-off)
 set(CRC32C_BUILD_TESTS OFF CACHE INTERNAL crc32c-gtest-off)
 set(CRC32C_BUILD_BENCHMARKS OFF CACHE INTERNAL crc32-benchmarks-off)
diff --git a/cmake/Nlohmann.cmake b/cmake/JsonSchemaValidator.cmake
similarity index 66%
copy from cmake/Nlohmann.cmake
copy to cmake/JsonSchemaValidator.cmake
index 9ad44c56c..6cad30dac 100644
--- a/cmake/Nlohmann.cmake
+++ b/cmake/JsonSchemaValidator.cmake
@@ -1,4 +1,3 @@
-#
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
 # distributed with this work for additional information
@@ -15,10 +14,15 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-#
 
-set(NLOHMANN_JSON_INCLUDE_DIR "${CMAKE_BINARY_DIR}/_deps/nlohmann/" CACHE STRING "" FORCE)
-if(NOT EXISTS "${NLOHMANN_JSON_INCLUDE_DIR}/nlohmann/json.hpp")
-    file(DOWNLOAD "https://github.com/nlohmann/json/releases/download/v3.10.5/json.hpp" "${NLOHMANN_JSON_INCLUDE_DIR}/nlohmann/json.hpp"
-            EXPECTED_HASH SHA256=e832d339d9e0c042e7dff807754769d778cf5d6ae9730ce21eed56de99cb5e86)
+include(FetchContent)
+
+FetchContent_Declare(json-schema-validator
+    URL https://github.com/pboettch/json-schema-validator/archive/2.2.0.tar.gz
+    URL_HASH SHA256=03897867bd757ecac1db7545babf0c6c128859655b496582a9cea4809c2260aa)
+
+FetchContent_MakeAvailable(json-schema-validator)
+
+if (NOT WIN32)
+  target_compile_options(nlohmann_json_schema_validator PRIVATE -Wno-error)
 endif()
diff --git a/libminifi/CMakeLists.txt b/libminifi/CMakeLists.txt
index 27d1c4f50..049e5ef41 100644
--- a/libminifi/CMakeLists.txt
+++ b/libminifi/CMakeLists.txt
@@ -57,7 +57,7 @@ if (NOT OPENSSL_OFF)
     set(TLS_SOURCES "src/utils/tls/*.cpp" "src/io/tls/*.cpp")
 endif()
 
-file(GLOB SOURCES "src/agent/agent_docs.cpp" "src/agent/build_description.cpp" "src/properties/*.cpp" "src/utils/file/*.cpp" "src/sitetosite/*.cpp"  "src/core/logging/*.cpp" "src/core/logging/internal/*.cpp" "src/core/logging/alert/*.cpp" "src/core/state/*.cpp" "src/core/state/nodes/*.cpp" "src/c2/protocols/*.cpp" "src/c2/triggers/*.cpp" "src/c2/*.cpp" "src/io/*.cpp" ${SOCKET_SOURCES} ${TLS_SOURCES} "src/core/controller/*.cpp" "src/controllers/*.cpp" "src/controllers/keyvalue/*.cpp" "src [...]
+file(GLOB SOURCES "src/agent/*.cpp" "src/properties/*.cpp" "src/utils/file/*.cpp" "src/sitetosite/*.cpp"  "src/core/logging/*.cpp" "src/core/logging/internal/*.cpp" "src/core/logging/alert/*.cpp" "src/core/state/*.cpp" "src/core/state/nodes/*.cpp" "src/c2/protocols/*.cpp" "src/c2/triggers/*.cpp" "src/c2/*.cpp" "src/io/*.cpp" ${SOCKET_SOURCES} ${TLS_SOURCES} "src/core/controller/*.cpp" "src/controllers/*.cpp" "src/controllers/keyvalue/*.cpp" "src/core/*.cpp"  "src/core/repository/*.cpp" " [...]
 # manually add this as it might not yet be present when this executes
 list(APPEND SOURCES "${CMAKE_CURRENT_BINARY_DIR}/agent_version.cpp")
 
diff --git a/libminifi/include/agent/JsonSchema.h b/libminifi/include/agent/JsonSchema.h
new file mode 100644
index 000000000..bc843ee6f
--- /dev/null
+++ b/libminifi/include/agent/JsonSchema.h
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace org::apache::nifi::minifi::docs {
+
+std::string generateJsonSchema();
+
+}  // namespace org::apache::nifi::minifi::docs
diff --git a/libminifi/include/core/PropertyValue.h b/libminifi/include/core/PropertyValue.h
index fc1af9f05..e40d77b53 100644
--- a/libminifi/include/core/PropertyValue.h
+++ b/libminifi/include/core/PropertyValue.h
@@ -106,6 +106,10 @@ class PropertyValue : public state::response::ValueNode {
     return convertImpl<bool>("bool");
   }
 
+  operator double() const {
+    return convertImpl<double>("double");
+  }
+
   const char* c_str() const {
     if (!isValueUsable()) {
       throw utils::internal::InvalidValueException("Cannot convert invalid value");
diff --git a/libminifi/src/agent/JsonSchema.cpp b/libminifi/src/agent/JsonSchema.cpp
new file mode 100644
index 000000000..857525e4e
--- /dev/null
+++ b/libminifi/src/agent/JsonSchema.cpp
@@ -0,0 +1,467 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "agent/JsonSchema.h"
+
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "agent/agent_version.h"
+#include "agent/build_description.h"
+#include "rapidjson/document.h"
+#include "rapidjson/prettywriter.h"
+#include "RemoteProcessorGroupPort.h"
+#include "utils/gsl.h"
+
+#include "range/v3/view/filter.hpp"
+#include "range/v3/view/transform.hpp"
+#include "range/v3/view/join.hpp"
+#include "range/v3/range/conversion.hpp"
+
+namespace org::apache::nifi::minifi::docs {
+
+static std::string escape(std::string str) {
+  utils::StringUtils::replaceAll(str, "\\", "\\\\");
+  utils::StringUtils::replaceAll(str, "\"", "\\\"");
+  utils::StringUtils::replaceAll(str, "\n", "\\n");
+  return str;
+}
+
+static std::string prettifyJson(const std::string& str) {
+  rapidjson::Document doc;
+  rapidjson::ParseResult res = doc.Parse(str.c_str(), str.length());
+  gsl_Assert(res);
+
+  rapidjson::StringBuffer buffer;
+
+  rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
+  doc.Accept(writer);
+
+  return std::string{buffer.GetString(), buffer.GetSize()};
+}
+
+void writePropertySchema(const core::Property& prop, std::ostream& out) {
+  out << "\"" << escape(prop.getName()) << "\" : {";
+  out << R"("description": ")" << escape(prop.getDescription()) << "\"";
+  if (const auto& values = prop.getAllowedValues(); !values.empty()) {
+    out << R"(, "enum": [)"
+        << (values
+            | ranges::views::transform([] (auto& val) {return '"' + escape(val.to_string()) + '"';})
+            | ranges::views::join(',')
+            | ranges::to<std::string>())
+        << "]";
+  }
+  if (const auto& def_value = prop.getDefaultValue(); !def_value.empty()) {
+    const auto& type = def_value.getTypeInfo();
+    // order is important as both DataSizeValue and TimePeriodValue's type_id is uint64_t
+    if (std::dynamic_pointer_cast<core::DataSizeValue>(def_value.getValue())
+        || std::dynamic_pointer_cast<core::TimePeriodValue>(def_value.getValue())) {  // NOLINT(bugprone-branch-clone)
+      // special value types
+      out << R"(, "type": "string", "default": ")" << escape(def_value.to_string()) << "\"";
+    } else if (type == state::response::Value::INT_TYPE
+        || type == state::response::Value::INT64_TYPE
+        || type == state::response::Value::UINT32_TYPE
+        || type == state::response::Value::UINT64_TYPE) {
+      out << R"(, "type": "integer", "default": )" << static_cast<int64_t>(def_value);
+    } else if (type == state::response::Value::DOUBLE_TYPE) {
+      out << R"(, "type": "number", "default": )" << static_cast<double>(def_value);
+    } else if (type == state::response::Value::BOOL_TYPE) {
+      out << R"(, "type": "boolean", "default": )" << (static_cast<bool>(def_value) ? "true" : "false");
+    } else {
+      out << R"(, "type": "string", "default": ")" << escape(def_value.to_string()) << "\"";
+    }
+  } else {
+    // no default value, no type information, fallback to string
+    out << R"(, "type": "string")";
+  }
+  out << "}";  // property.getName()
+}
+
+template<typename PropertyContainer>
+void writeProperties(const PropertyContainer& props, bool supports_dynamic, std::ostream& out) {
+  out << R"("Properties": {)"
+        << R"("type": "object",)"
+        << R"("additionalProperties": )" << (supports_dynamic? "true" : "false") << ","
+        << R"("required": [)"
+        << (props
+            | ranges::views::filter([] (auto& prop) {return prop.getRequired() && prop.getDefaultValue().empty();})
+            | ranges::views::transform([] (auto& prop) {return '"' + escape(prop.getName()) + '"';})
+            | ranges::views::join(',')
+            | ranges::to<std::string>())
+        << "]";
+
+  out << R"(, "properties": {)";
+  for (size_t prop_idx = 0; prop_idx < props.size(); ++prop_idx) {
+    const auto& property = props[prop_idx];
+    if (prop_idx != 0) out << ",";
+    writePropertySchema(property, out);
+  }
+  out << "}";  // "properties"
+  out << "}";  // "Properties"
+}
+
+static std::string buildSchema(const std::unordered_map<std::string, std::string>& relationships, const std::string& processors, const std::string& controller_services) {
+  std::stringstream all_rels;
+  for (const auto& [name, rels] : relationships) {
+    all_rels << "\"relationships-" << escape(name) << "\": " << rels << ", ";
+  }
+
+  std::stringstream remote_port_props;
+  writeProperties(minifi::RemoteProcessorGroupPort::properties(), minifi::RemoteProcessorGroupPort::SupportsDynamicProperties, remote_port_props);
+
+  std::string process_group_properties = R"(
+    "Processors": {
+      "type": "array",
+      "items": {"$ref": "#/definitions/processor"}
+    },
+    "Connections": {
+      "type": "array",
+      "items": {"$ref": "#/definitions/connection"}
+    },
+    "Controller Services": {
+      "type": "array",
+      "items": {"$ref": "#/definitions/controller_service"}
+    },
+    "Remote Process Groups": {
+      "type": "array",
+      "items": {"$ref": "#/definitions/remote_process_group"}
+    },
+    "Process Groups": {
+      "type": "array",
+      "items": {"$ref": "#/definitions/simple_process_group"}
+    },
+    "Funnels": {
+      "type": "array",
+      "items": {"$ref": "#/definitions/funnel"}
+    },
+    "Input Ports": {
+      "type": "array",
+      "items": {"$ref": "#/definitions/port"}
+    },
+    "Output Ports": {
+      "type": "array",
+      "items": {"$ref": "#/definitions/port"}
+    }
+  )";
+
+  std::stringstream cron_pattern;
+  {
+    const char* all = "\\\\*";
+    const char* any = "\\\\?";
+    const char* increment = "(-?[0-9]+)";
+    const char* secs = "([0-5]?[0-9])";
+    const char* mins = "([0-5]?[0-9])";
+    const char* hours = "(1?[0-9]|2[0-3])";
+    const char* days = "([1-2]?[0-9]|3[0-1])";
+    const char* months = "([0-9]|1[0-2]|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)";
+    const char* weekdays = "([0-7]|sun|mon|tue|wed|thu|fri|sat)";
+    const char* years = "([0-9]+)";
+
+    auto makeCommon = [&] (const char* pattern) {
+      std::stringstream common;
+      common << all << "|" << any
+        << "|" << pattern << "(," << pattern << ")*"
+        << "|" << pattern << "-" << pattern
+        << "|" << "(" << all << "|" << pattern << ")" << "/" << increment;
+      return std::move(common).str();
+    };
+
+    cron_pattern << "^"
+      << "(" << makeCommon(secs) << ")"
+      << " (" << makeCommon(mins) << ")"
+      << " (" << makeCommon(hours) << ")"
+      << " (" << makeCommon(days) << "|LW|L|L-" << days << "|" << days << "W" << ")"
+      << " (" << makeCommon(months) << ")"
+      << " (" << makeCommon(weekdays) << "|" << weekdays << "?L|" << weekdays << "#" << "[1-5]" << ")"
+      << "( (" << makeCommon(years) << "))?"
+      << "$";
+  }
+
+  // the schema specification does not allow case-insensitive regex
+  std::stringstream cron_pattern_case_insensitive;
+  for (char ch : cron_pattern.str()) {
+    if (std::isalpha(static_cast<unsigned char>(ch))) {
+      cron_pattern_case_insensitive << "["
+          << static_cast<char>(std::tolower(static_cast<unsigned char>(ch)))
+          << static_cast<char>(std::toupper(static_cast<unsigned char>(ch)))
+          << "]";
+    } else {
+      cron_pattern_case_insensitive << ch;
+    }
+  }
+
+  return prettifyJson(R"(
+{
+  "$schema": "http://json-schema.org/draft-07/schema",
+  "definitions": {)" + std::move(all_rels).str() + R"(
+    "datasize": {
+      "type": "string",
+      "pattern": "^\\s*[0-9]+\\s*(B|K|M|G|T|P|KB|MB|GB|TB|PB)\\s*$"
+    },
+    "uuid": {
+      "type": "string",
+      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
+      "default": "00000000-0000-0000-0000-000000000000"
+    },
+    "cron_pattern": {
+      "type": "string",
+      "pattern": ")" + std::move(cron_pattern_case_insensitive).str() + R"("
+    },
+    "remote_port": {
+      "type": "object",
+      "required": ["name", "id", "Properties"],
+      "properties": {
+        "name": {"type": "string"},
+        "id": {"$ref": "#/definitions/uuid"},
+        "max concurrent tasks": {"type": "integer"},
+        )" + std::move(remote_port_props).str() +  R"(
+      }
+    },
+    "port": {
+      "type": "object",
+      "required": ["name", "id"],
+      "properties": {
+        "name": {"type": "string"},
+        "id": {"$ref": "#/definitions/uuid"}
+      }
+    },
+    "time": {
+      "type": "string",
+      "pattern": "^\\s*[0-9]+\\s*(ns|nano|nanos|nanoseconds|nanosecond|us|micro|micros|microseconds|microsecond|msec|ms|millisecond|milliseconds|msecs|millis|milli|sec|s|second|seconds|secs|min|m|mins|minute|minutes|h|hr|hour|hrs|hours|d|day|days)\\s*$"
+    },
+    "controller_service": {"allOf": [{
+      "type": "object",
+      "required": ["name", "id", "class"],
+      "properties": {
+        "name": {"type": "string"},
+        "class": {"type": "string"},
+        "id": {"$ref": "#/definitions/uuid"}
+      }
+    }, )" + controller_services + R"(]},
+    "processor": {"allOf": [{
+      "type": "object",
+      "required": ["name", "id", "class", "scheduling strategy"],
+      "additionalProperties": false,
+      "properties": {
+        "name": {"type": "string"},
+        "id": {"$ref": "#/definitions/uuid"},
+        "class": {"type": "string"},
+        "max concurrent tasks": {"type": "integer", "default": 1},
+        "penalization period": {"$ref": "#/definitions/time"},
+        "yield period": {"$ref": "#/definitions/time"},
+        "run duration nanos": {"$ref": "#/definitions/time"},
+        "Properties": {},
+        "scheduling strategy": {"enum": ["EVENT_DRIVEN", "TIMER_DRIVEN", "CRON_DRIVEN"]},
+        "scheduling period": {},
+        "auto-terminated relationships list": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "uniqueItems": true
+        }
+      }}, {
+        "if": {"properties": {"scheduling strategy": {"const": "EVENT_DRIVEN"}}},
+        "then": {"properties": {"scheduling period": false}}
+      }, {
+        "if": {"properties": {"scheduling strategy": {"const": "TIMER_DRIVEN"}}},
+        "then": {"required": ["scheduling period"], "properties": {"scheduling period": {"$ref": "#/definitions/time"}}}
+      }, {
+        "if": {"properties": {"scheduling strategy": {"const": "CRON_DRIVEN"}}},
+        "then": {"required": ["scheduling period"], "properties": {"scheduling period": {"$ref": "#/definitions/cron_pattern"}}}
+      })" + (!processors.empty() ? ", " : "") + processors + R"(]
+    },
+    "remote_process_group": {"allOf": [{
+      "type": "object",
+      "required": ["name", "id", "Input Ports"],
+      "properties": {
+        "name": {"type": "string"},
+        "id": {"$ref": "#/definitions/uuid"},
+        "url": {"type": "string"},
+        "yield period": {"$ref": "#/definitions/time"},
+        "timeout": {"$ref": "#/definitions/time"},
+        "local network interface": {"type": "string"},
+        "transport protocol": {"enum": ["HTTP", "RAW"]},
+        "Input Ports": {
+          "type": "array",
+          "items": {"$ref": "#/definitions/remote_port"}
+        },
+        "Output Ports": {
+          "type": "array",
+          "items": {"$ref": "#/definitions/remote_port"}
+        }
+      }
+    }, {
+      "if": {"properties": {"transport protocol": {"const": "HTTP"}}},
+      "then": {"properties": {
+        "proxy host": {"type": "string"},
+        "proxy user": {"type": "string"},
+        "proxy password": {"type": "string"},
+        "proxy port": {"type": "integer"}
+      }}
+    }]},
+    "connection": {
+      "type": "object",
+      "additionalProperties": false,
+      "required": ["name", "id", "source id", "source relationship names", "destination id"],
+      "properties": {
+        "name": {"type": "string"},
+        "id": {"$ref": "#/definitions/uuid"},
+        "source name": {"type": "string"},
+        "source id": {"$ref": "#/definitions/uuid"},
+        "source relationship names": {
+          "type": "array",
+          "items": {"type": "string"}
+        },
+        "destination name": {"type": "string"},
+        "destination id": {"$ref": "#/definitions/uuid"},
+        "max work queue size": {"type": "integer", "default": 10000},
+        "max work queue data size": {"$ref": "#/definitions/datasize", "default": "10 MB"},
+        "flowfile expiration": {"$ref": "#/definitions/time", "default": "0 ms"}
+      }
+    },
+    "funnel": {
+      "type": "object",
+      "required": ["id"],
+      "properties": {
+        "id": {"$ref": "#/definitions/uuid"},
+        "name": {"type": "string"}
+      }
+    },
+    "simple_process_group": {
+      "type": "object",
+      "required": ["name"],
+      "additionalProperties": false,
+      "properties": {
+        "name": {"type": "string"},
+        "version": {"type": "integer"},
+        "onschedule retry interval": {"$ref": "#/definitions/time"},
+        )" + process_group_properties + R"(
+      }
+    },
+    "root_process_group": {
+      "type": "object",
+      "required": ["Flow Controller"],
+      "additionalProperties": false,
+      "properties": {
+        "$schema": {"type": "string"},
+        "Flow Controller": {
+          "type": "object",
+          "required": ["name"],
+          "properties": {
+            "name": {"type": "string"},
+            "version": {"type": "integer"},
+            "onschedule retry interval": {"$ref": "#/definitions/time"}
+          }
+        },
+        )" + process_group_properties + R"(
+      }
+    }
+  },
+  "$ref": "#/definitions/root_process_group"
+}
+)");
+}
+
+std::string generateJsonSchema() {
+  std::unordered_map<std::string, std::string> relationships;
+  std::vector<std::string> proc_schemas;
+  auto putProcSchema = [&] (const ClassDescription& proc) {
+    std::stringstream schema;
+    schema
+        << "{"
+        << R"("if": {"properties": {"class": {"const": ")" << escape(proc.short_name_) << "\"}}},"
+        << R"("then": {)"
+        << R"("required": ["Properties"],)"
+        << R"("properties": {)";
+
+    if (proc.isSingleThreaded_) {
+      schema << R"("max concurrent tasks": {"const": 1},)";
+    }
+
+    schema << R"("auto-terminated relationships list": {"items": {"$ref": "#/definitions/relationships-)" << escape(proc.short_name_) << "\"}},";
+    {
+      std::stringstream rel_schema;
+      rel_schema << R"({"anyOf": [)";
+      if (proc.dynamic_relationships_) {
+        rel_schema << R"({"type": "string"})";
+      }
+      for (size_t rel_idx = 0; rel_idx < proc.class_relationships_.size(); ++rel_idx) {
+        if (rel_idx != 0 || proc.dynamic_relationships_) rel_schema << ", ";
+        rel_schema << R"({"const": ")" << escape(proc.class_relationships_[rel_idx].getName()) << "\"}";
+      }
+      rel_schema << "]}";
+      relationships[proc.short_name_] = std::move(rel_schema).str();
+    }
+
+    writeProperties(proc.class_properties_, proc.dynamic_properties_, schema);
+
+    schema << "}";  // "properties"
+    schema << "}";  // "then"
+    schema << "}";  // if-block
+
+    proc_schemas.push_back(std::move(schema).str());
+  };
+
+  std::vector<std::string> controller_services;
+  auto putControllerService = [&] (const ClassDescription& service) {
+    std::stringstream schema;
+    schema
+        << "{"
+        << R"("if": {"properties": {"class": {"const": ")" << escape(service.short_name_) << "\"}}},"
+        << R"("then": {)"
+        << R"("required": ["Properties"],)"
+        << R"("properties": {)";
+
+    writeProperties(service.class_properties_, service.dynamic_properties_, schema);
+
+    schema << "}";  // "properties"
+    schema << "}";  // "then"
+    schema << "}";  // if-block
+
+    controller_services.push_back(std::move(schema).str());
+  };
+
+  const auto& descriptions = AgentDocs::getClassDescriptions();
+  for (const std::string& group : AgentBuild::getExtensions()) {
+    auto it = descriptions.find(group);
+    if (it == descriptions.end()) {
+      continue;
+    }
+    for (const auto& proc : it->second.processors_) {
+      putProcSchema(proc);
+    }
+    for (const auto& service : it->second.controller_services_) {
+      putControllerService(service);
+    }
+  }
+
+  for (const auto& bundle : ExternalBuildDescription::getExternalGroups()) {
+    auto description = ExternalBuildDescription::getClassDescriptions(bundle.artifact);
+    for (const auto& proc : description.processors_) {
+      putProcSchema(proc);
+    }
+    for (const auto& service : description.controller_services_) {
+      putControllerService(service);
+    }
+  }
+
+  return buildSchema(relationships, utils::StringUtils::join(", ", proc_schemas), utils::StringUtils::join(", ", controller_services));
+}
+
+}  // namespace org::apache::nifi::minifi::docs
diff --git a/cmake/Nlohmann.cmake b/libminifi/test/schema-tests/CMakeLists.txt
similarity index 52%
rename from cmake/Nlohmann.cmake
rename to libminifi/test/schema-tests/CMakeLists.txt
index 9ad44c56c..3b8dbe0c2 100644
--- a/cmake/Nlohmann.cmake
+++ b/libminifi/test/schema-tests/CMakeLists.txt
@@ -17,8 +17,18 @@
 # under the License.
 #
 
-set(NLOHMANN_JSON_INCLUDE_DIR "${CMAKE_BINARY_DIR}/_deps/nlohmann/" CACHE STRING "" FORCE)
-if(NOT EXISTS "${NLOHMANN_JSON_INCLUDE_DIR}/nlohmann/json.hpp")
-    file(DOWNLOAD "https://github.com/nlohmann/json/releases/download/v3.10.5/json.hpp" "${NLOHMANN_JSON_INCLUDE_DIR}/nlohmann/json.hpp"
-            EXPECTED_HASH SHA256=e832d339d9e0c042e7dff807754769d778cf5d6ae9730ce21eed56de99cb5e86)
-endif()
+include(JsonSchemaValidator)
+
+file(GLOB SCHEMA_TESTS  "*.cpp")
+SET(SCHEMA_TEST_COUNT 0)
+FOREACH(testfile ${SCHEMA_TESTS})
+  get_filename_component(testfilename "${testfile}" NAME_WE)
+  add_executable("${testfilename}" "${testfile}")
+  createTests("${testfilename}")
+  target_link_libraries(${testfilename} ${CATCH_MAIN_LIB})
+  target_link_libraries(${testfilename} minifi-standard-processors)
+  target_link_libraries(${testfilename} nlohmann_json_schema_validator)
+  MATH(EXPR SCHEMA_TEST_COUNT "${SCHEMA_TEST_COUNT}+1")
+  add_test(NAME "${testfilename}" COMMAND "${testfilename}" WORKING_DIRECTORY ${TEST_DIR})
+ENDFOREACH()
+message("-- Finished building ${SCHEMA_TEST_COUNT} Json Schema related test file(s)...")
diff --git a/libminifi/test/schema-tests/SchemaTests.cpp b/libminifi/test/schema-tests/SchemaTests.cpp
new file mode 100644
index 000000000..d4907ec1c
--- /dev/null
+++ b/libminifi/test/schema-tests/SchemaTests.cpp
@@ -0,0 +1,258 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fstream>
+
+#include "../TestBase.h"
+#include "../Catch.h"
+#include "../agent/JsonSchema.h"
+#include "nlohmann/json-schema.hpp"
+#include "utils/RegexUtils.h"
+#include "utils/StringUtils.h"
+
+struct JsonError {
+  std::string path;
+  std::string error;
+};
+
+class ErrorHandler : public nlohmann::json_schema::error_handler {
+ public:
+  explicit ErrorHandler(std::function<void(const JsonError&)> handler)
+    : handler_(std::move(handler)) {}
+  void error(const nlohmann::json::json_pointer& ptr, const nlohmann::json& /*instance*/, const std::string& message) override {
+    handler_(JsonError{ptr.to_string(), message});
+  }
+
+ private:
+  std::function<void(const JsonError&)> handler_;
+};
+
+void extractExpectedErrors(nlohmann::json& node, const std::string& path, std::unordered_map<std::string, std::string>& errors) {
+  if (node.is_object() && node.contains("$err")) {
+    errors[path] = node["$err"].get<std::string>();
+    node = node["$value"];
+  }
+  if (node.is_object() || node.is_array()) {
+    for (auto& [key, val] : node.items()) {
+      extractExpectedErrors(val, utils::StringUtils::join_pack(path, "/", key), errors);
+    }
+  }
+}
+
+TEST_CASE("The generated JSON schema matches a valid json flow") {
+  const nlohmann::json config_schema = nlohmann::json::parse(minifi::docs::generateJsonSchema());
+
+  nlohmann::json_schema::json_validator validator;
+  validator.set_root_schema(config_schema);
+
+  auto config_json = R"(
+    {
+      "Flow Controller": {"name": "Test"},
+      "Processors": [
+        {
+          "id": "00000000-0000-0000-0000-000000000000",
+          "class": "GenerateFlowFile",
+          "name": "Proc1",
+          "scheduling strategy": "TIMER_DRIVEN",
+          "scheduling period": "1 min",
+          "Properties": {}
+        }
+      ],
+      "Connections": [
+        {
+          "id": "00000000-0000-0000-0000-000000000000",
+          "name": "Conn1",
+          "source id": "00000000-0000-0000-0000-000000000000",
+          "source relationship names": ["success"],
+          "destination id": "00000000-0000-0000-0000-000000000000"
+        }
+      ],
+      "Input Ports": [
+        {"id": "00000000-0000-0000-0000-000000000000", "name": "In1"}
+      ],
+      "Output Ports": [
+        {"id": "00000000-0000-0000-0000-000000000000", "name": "Out1"}
+      ],
+      "Funnels": [
+        {"id": "00000000-0000-0000-0000-000000000000", "name": "Fun1"}
+      ],
+      "Process Groups": [
+        {
+          "name": "Group1",
+          "Processors": [],
+          "Connections": [],
+          "Process Groups": []
+        }
+      ],
+      "Remote Process Groups": [
+        {
+          "id": "00000000-0000-0000-0000-000000000000",
+          "name": "RPG1",
+          "Input Ports": [{
+            "id": "00000000-0000-0000-0000-000000000000",
+            "name": "RIn1",
+            "Properties": {
+              "Host Name": "localhost"
+            }
+          }]
+        }
+      ],
+      "Controller Services": [
+        {
+          "id": "00000000-0000-0000-0000-000000000000",
+          "class": "SSLContextService",
+          "name": "Service1",
+          "Properties": {
+            "Client Certificate": "",
+            "Private Key": "",
+            "Passphrase": "",
+            "CA Certificate": ""
+          }
+        }
+      ]
+    }
+  )"_json;
+
+  validator.validate(config_json);
+}
+
+TEST_CASE("The JSON schema detects invalid values in the json flow") {
+  const nlohmann::json config_schema = nlohmann::json::parse(minifi::docs::generateJsonSchema());
+
+  nlohmann::json_schema::json_validator validator;
+  validator.set_root_schema(config_schema);
+
+  // the objects of type {"$err": <error>, "$value": <value>} are special
+  // in the sense that they are preprocessed, replaced by <value> and we expect
+  // a validation error at the position of this object that matches the
+  // regex <error>
+  auto config_json = R"(
+    {
+      "Flow Controller": {"name": "Test"},
+      "Processors": [
+        {"$err": "property 'scheduling period' not found", "$value": {
+          "id": "00000000-0000-0000-0000-000000000000",
+          "class": "GenerateFlowFile",
+          "name": "Proc1",
+          "scheduling strategy": "TIMER_DRIVEN",
+          "Properties": {}
+        }},
+        {
+          "id": "00000000-0000-0000-0000-000000000000",
+          "class": "GenerateFlowFile",
+          "name": "Proc1",
+          "scheduling strategy": "TIMER_DRIVEN",
+          "scheduling period": "1 min",
+          "Properties": {
+            "Batch Size": {"$value": "not a number", "$err": "unexpected instance type"}
+          }
+        },
+        {
+          "id": "00000000-0000-0000-0000-000000000000",
+          "class": "GenerateFlowFile",
+          "name": "Proc1",
+          "scheduling strategy": "TIMER_DRIVEN",
+          "scheduling period": "1 min",
+          "Properties": {"$err": "", "$value": {
+            "No such property": 5
+          }}
+        }
+      ],
+      "Connections": [
+        {"$err": "property 'name' not found", "$value": {
+          "id": {"$value": "00000000-0000-0000-0000-00000000000", "$err": ""},
+          "source id": "00000000-0000-0000-0000-000000000000",
+          "source relationship names": ["success"],
+          "destination id": "00000000-0000-0000-0000-000000000000"
+        }}
+      ],
+      "Input Ports": [
+        {"id": "00000000-0000-0000-0000-000000000000", "name": {"$value": 5, "$err": "unexpected instance type"}}
+      ],
+      "Output Ports": [
+        {"id": "00000000-0000-0000-0000-000000000000", "name": "Out1"}
+      ],
+      "Funnels": [
+        {"id": {"$value": 5, "$err": "unexpected instance type"}}
+      ],
+      "Process Groups": [
+        {"$err": "", "$value": {
+          "name": "Group1",
+          "no such property": []
+        }}
+      ],
+      "Remote Process Groups": [
+        {"$err": "property 'Input Ports' not found", "$value": {
+          "id": "00000000-0000-0000-0000-000000000000",
+          "name": "RPG1"
+        }}
+      ],
+      "Controller Services": [
+        {
+          "id": {"$value": "00000000-0000-0000-0000-00000000000", "$err": ""},
+          "class": "SSLContextService",
+          "name": "Service1",
+          "Properties": {
+            "Client Certificate": 6,
+            "Private Key": "",
+            "Passphrase": "",
+            "CA Certificate": ""
+          }
+        },
+        {
+          "id": "00000000-0000-0000-0000-000000000001",
+          "class": "SSLContextService",
+          "name": "Service1",
+          "Properties": {
+            "Client Certificate": {"$value": 6, "$err": "unexpected instance type"},
+            "Private Key": "",
+            "Passphrase": "",
+            "CA Certificate": ""
+          }
+        },
+        {"$value": "kenyer", "$err": "unexpected instance type"}
+      ]
+    }
+  )"_json;
+
+  std::unordered_map<std::string, std::string> errors;
+  extractExpectedErrors(config_json, "", errors);
+
+  ErrorHandler err_handler{[&] (auto err) {
+    auto it = errors.find(err.path);
+    if (it == errors.end()) {
+      throw std::logic_error("Unexpected error in json flow at " + err.path + ": " + err.error);
+    }
+    if (!it->second.empty()) {
+      minifi::utils::Regex re(it->second);
+      if (!minifi::utils::regexSearch(err.error, re)) {
+        throw std::logic_error("Error in json flow at " + err.path + " does not match expected pattern, expected: '" + it->second + "', actual: " + err.error);
+      }
+    }
+    errors.erase(it);
+  }};
+  validator.validate(config_json, err_handler);
+
+  // all expected errors should have been processed
+  if (!errors.empty()) {
+    for (const auto& [path, err] : errors) {
+      std::cerr << "Expected error at " << path << ": " << err << std::endl;
+    }
+    throw std::logic_error("There were some expected errors that did not occur");
+  }
+}
diff --git a/minifi_main/MiNiFiMain.cpp b/minifi_main/MiNiFiMain.cpp
index 8dee11d0f..b41935130 100644
--- a/minifi_main/MiNiFiMain.cpp
+++ b/minifi_main/MiNiFiMain.cpp
@@ -63,6 +63,7 @@
 #include "FlowController.h"
 #include "AgentDocs.h"
 #include "MainHelper.h"
+#include "agent/JsonSchema.h"
 
 namespace minifi = org::apache::nifi::minifi;
 namespace core = minifi::core;
@@ -119,6 +120,15 @@ void dumpDocs(const std::shared_ptr<minifi::Configure> &configuration, const std
   docsCreator.generate(dir, out);
 }
 
+void writeJsonSchema(const std::shared_ptr<minifi::Configure> &configuration, std::ostream& out) {
+  auto pythoncreator = core::ClassLoader::getDefaultClassLoader().instantiate("PythonCreator", "PythonCreator");
+  if (nullptr != pythoncreator) {
+    pythoncreator->configure(configuration);
+  }
+
+  out << minifi::docs::generateJsonSchema();
+}
+
 int main(int argc, char **argv) {
 #ifdef WIN32
   RunAsServiceIfNeeded();
@@ -252,7 +262,7 @@ int main(int argc, char **argv) {
         exit(1);
       }
 
-      std::cerr << "Dumping docs to " << argv[2] << std::endl;
+      std::cout << "Dumping docs to " << argv[2] << std::endl;
       if (argc == 4) {
         std::string filepath;
         std::string filename;
@@ -269,6 +279,21 @@ int main(int argc, char **argv) {
       exit(0);
     }
 
+    if (argc >= 2 && std::string("schema") == argv[1]) {
+      if (argc != 3) {
+        std::cerr << "Malformed schema command, expected '<minifiexe> schema <output-file>'" << std::endl;
+        std::exit(1);
+      }
+
+      std::cout << "Writing json schema to " << argv[2] << std::endl;
+
+      {
+        std::ofstream schema_file{argv[2]};
+        writeJsonSchema(configure, schema_file);
+      }
+      std::exit(0);
+    }
+
     if (configure->get(minifi::Configure::nifi_graceful_shutdown_seconds, graceful_shutdown_seconds)) {
       try {
         stop_wait_time = std::stoi(graceful_shutdown_seconds);
diff --git a/thirdparty/google-cloud-cpp/nlohmann_lib_as_interface.patch b/thirdparty/google-cloud-cpp/nlohmann_lib_as_interface.patch
index 56d951a85..3c48b7d21 100644
--- a/thirdparty/google-cloud-cpp/nlohmann_lib_as_interface.patch
+++ b/thirdparty/google-cloud-cpp/nlohmann_lib_as_interface.patch
@@ -1,13 +1,26 @@
-diff --git a/cmake/IncludeNlohmannJson.cmake b/cmake/IncludeNlohmannJson.cmake
-index db8056ae0..613f18b97 100644
---- a/cmake/IncludeNlohmannJson.cmake
-+++ b/cmake/IncludeNlohmannJson.cmake
-@@ -23,7 +23,7 @@ function (find_nlohmann_json)
-     # library that is all we need.
-     find_path(GOOGLE_CLOUD_CPP_NLOHMANN_JSON_HEADER "nlohmann/json.hpp"
-               REQUIRED)
+diff -rupN orig/cmake/IncludeNlohmannJson.cmake patched/cmake/IncludeNlohmannJson.cmake
+--- google-cloud-cpp-1.37.0/cmake/IncludeNlohmannJson.cmake	2022-03-01 19:09:39.000000000 +0100
++++ google-cloud-cpp-1.37.0-new/cmake/IncludeNlohmannJson.cmake	2022-12-09 10:32:45.000000000 +0100
+@@ -14,21 +14,4 @@
+ # limitations under the License.
+ # ~~~
+ 
+-function (find_nlohmann_json)
+-    find_package(nlohmann_json CONFIG QUIET)
+-    if (nlohmann_json_FOUND)
+-        return()
+-    endif ()
+-    # As a fall back, try finding the header. Since this is a header-only
+-    # library that is all we need.
+-    find_path(GOOGLE_CLOUD_CPP_NLOHMANN_JSON_HEADER "nlohmann/json.hpp"
+-              REQUIRED)
 -    add_library(nlohmann_json::nlohmann_json UNKNOWN IMPORTED)
-+    add_library(nlohmann_json::nlohmann_json INTERFACE IMPORTED)
-     set_property(
-         TARGET nlohmann_json::nlohmann_json
-         APPEND
+-    set_property(
+-        TARGET nlohmann_json::nlohmann_json
+-        APPEND
+-        PROPERTY INTERFACE_INCLUDE_DIRECTORIES
+-                 ${GOOGLE_CLOUD_CPP_NLOHMANN_JSON_HEADER})
+-endfunction ()
+-
+-find_nlohmann_json()
++find_package(nlohmann_json REQUIRED)


[nifi-minifi-cpp] 02/03: MINIFICPP-1862 use std::filesystem::path instead of std::string where appropriate

Posted by lo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

lordgamez pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit e8906515e8e73dc1485c6527c3a58795ac6899ea
Author: Martin Zink <ma...@apache.org>
AuthorDate: Tue Aug 9 11:51:53 2022 +0200

    MINIFICPP-1862 use std::filesystem::path instead of std::string where appropriate
    
    - ListSFTPTests and PutSFTPTests change string / to operator/
    - simplify JniReferenceObjects.h with ranges
    - removed unused read_size_ from JniByteInputStream
    
    Co-Authored-By: Ferenc Gerlits <fg...@gmail.com>
    Signed-off-by: Gabor Gyimesi <ga...@gmail.com>
    
    This closes #1424
---
 CMakeSettings.json                                 |   4 -
 Windows.md                                         |   2 +-
 cmake/BuildTests.cmake                             |   7 -
 docker/Dockerfile                                  |   2 +-
 docker/bionic/Dockerfile                           |   2 +-
 docker/centos/Dockerfile                           |   2 +-
 docker/fedora/Dockerfile                           |   2 +-
 docker/focal/Dockerfile                            |   2 +-
 encrypt-config/EncryptConfig.cpp                   |  28 +-
 encrypt-config/EncryptConfig.h                     |   7 +-
 encrypt-config/tests/ConfigFileTests.cpp           |   4 +-
 extensions/aws/processors/FetchS3Object.cpp        |   8 +-
 extensions/aws/s3/S3Wrapper.cpp                    |   5 +-
 extensions/aws/s3/S3Wrapper.h                      |  18 +-
 .../azure/processors/ListAzureDataLakeStorage.cpp  |   4 +-
 extensions/azure/storage/AzureDataLakeStorage.cpp  |   9 +-
 extensions/azure/storage/AzureDataLakeStorage.h    |   4 +-
 extensions/civetweb/tests/ListenHTTPTests.cpp      |  51 ++--
 extensions/coap/tests/CoapIntegrationBase.h        |   4 +-
 .../tests/ExpressionLanguageTests.cpp              |  21 +-
 extensions/http-curl/client/HTTPClient.cpp         |   6 +-
 .../tests/C2ClearCoreComponentStateTest.cpp        |  16 +-
 extensions/http-curl/tests/C2ConfigEncryption.cpp  |   4 +-
 .../tests/C2DescribeCoreComponentStateTest.cpp     |  14 +-
 .../http-curl/tests/C2FetchFlowIfMissingTest.cpp   |   8 +-
 extensions/http-curl/tests/C2MetricsTest.cpp       |   5 +-
 extensions/http-curl/tests/C2NullConfiguration.cpp |   7 +-
 extensions/http-curl/tests/C2UpdateAssetTest.cpp   |   2 +-
 .../http-curl/tests/C2VerifyServeResults.cpp       |  12 +-
 .../tests/ControllerServiceIntegrationTests.cpp    |   2 +-
 extensions/http-curl/tests/HTTPSiteToSiteTests.cpp |   6 +-
 .../http-curl/tests/HttpPostIntegrationTest.cpp    |  14 +-
 extensions/http-curl/tests/SiteToSiteRestTest.cpp  |  14 +-
 .../http-curl/tests/TimeoutHTTPSiteToSiteTests.cpp |   6 +-
 extensions/http-curl/tests/VerifyInvokeHTTP.h      |   6 +-
 .../http-curl/tests/VerifyInvokeHTTPPostTest.cpp   |   2 +-
 extensions/jni/JVMCreator.h                        |   2 +-
 extensions/jni/jvm/JVMLoader.h                     |  18 +-
 extensions/jni/jvm/JavaControllerService.h         |   4 +-
 extensions/jni/jvm/JniReferenceObjects.h           | 106 +++----
 extensions/libarchive/ArchiveMetadata.h            |   2 +-
 extensions/libarchive/ArchiveTests.h               |   9 +-
 extensions/libarchive/FocusArchiveEntry.cpp        |   8 +-
 extensions/libarchive/ManipulateArchive.cpp        |  18 +-
 extensions/libarchive/UnfocusArchiveEntry.cpp      |  16 +-
 extensions/librdkafka/KafkaProcessorBase.cpp       |  12 +-
 extensions/librdkafka/PublishKafka.cpp             |  12 +-
 extensions/pcap/CapturePacket.cpp                  |  10 +-
 extensions/pcap/CapturePacket.h                    |  22 +-
 .../pdh/tests/PerformanceDataMonitorTests.cpp      |  20 +-
 .../rocksdb-repos/DatabaseContentRepository.cpp    |   2 +-
 extensions/rocksdb-repos/FlowFileRepository.cpp    |  14 +-
 extensions/rocksdb-repos/FlowFileRepository.h      |   6 +-
 extensions/script/ExecuteScript.cpp                |   5 +-
 extensions/script/ScriptEngine.h                   |   5 +-
 extensions/script/lua/LuaScriptEngine.cpp          |   4 +-
 extensions/script/lua/LuaScriptEngine.h            |   6 +-
 .../script/python/ExecutePythonProcessor.cpp       |   3 +-
 extensions/script/python/PythonCreator.h           |  34 +--
 extensions/script/python/PythonScriptEngine.cpp    |   9 +-
 .../script/tests/ExecutePythonProcessorTests.cpp   |  63 ++--
 .../TestExecuteScriptProcessorWithLuaScript.cpp    |  50 ++-
 .../TestExecuteScriptProcessorWithPythonScript.cpp | 285 +++++++++--------
 extensions/sftp/processors/FetchSFTP.cpp           |  39 +--
 extensions/sftp/processors/ListSFTP.cpp            |  36 +--
 extensions/sftp/processors/ListSFTP.h              |   6 +-
 extensions/sftp/processors/PutSFTP.cpp             |  63 ++--
 extensions/sftp/tests/CMakeLists.txt               |   2 +-
 extensions/sftp/tests/FetchSFTPTests.cpp           |  84 ++---
 extensions/sftp/tests/ListSFTPTests.cpp            |  61 ++--
 extensions/sftp/tests/ListThenFetchSFTPTests.cpp   |  66 +---
 extensions/sftp/tests/PutSFTPTests.cpp             | 121 ++++----
 extensions/sftp/tests/tools/SFTPTestServer.cpp     |  22 +-
 extensions/sftp/tests/tools/SFTPTestServer.h       |  16 +-
 .../processors/ExecuteProcess.cpp                  |   8 +-
 .../processors/ExecuteProcess.h                    |   2 +-
 .../standard-processors/processors/FetchFile.cpp   |  49 ++-
 .../standard-processors/processors/FetchFile.h     |  16 +-
 .../standard-processors/processors/GetFile.cpp     |  57 ++--
 .../standard-processors/processors/GetFile.h       |  10 +-
 .../standard-processors/processors/ListFile.cpp    |  49 +--
 .../standard-processors/processors/ListFile.h      |  12 +-
 .../standard-processors/processors/PutFile.cpp     | 101 +++---
 .../standard-processors/processors/PutFile.h       |  14 +-
 .../standard-processors/processors/PutTCP.cpp      |   6 +-
 .../standard-processors/processors/TailFile.cpp    | 168 +++++-----
 .../standard-processors/processors/TailFile.h      |  35 +--
 .../tests/integration/SecureSocketGetTCPTest.cpp   |   6 +-
 .../tests/integration/TailFileTest.cpp             |  19 +-
 .../tests/unit/AttributesToJSONTests.cpp           |  22 +-
 .../tests/unit/ExecuteProcessTests.cpp             |  12 +-
 .../tests/unit/ExtractTextTests.cpp                |  16 +-
 .../tests/unit/FetchFileTests.cpp                  |  83 ++---
 .../tests/unit/GenerateFlowFileTests.cpp           |  42 +--
 .../tests/unit/GetFileTests.cpp                    |  30 +-
 .../tests/unit/HashContentTest.cpp                 |  12 +-
 .../tests/unit/ListFileTests.cpp                   |  61 ++--
 .../tests/unit/ListenSyslogTests.cpp               |  13 +-
 .../tests/unit/ListenTcpTests.cpp                  |  25 +-
 .../tests/unit/ProcessorTests.cpp                  |  60 ++--
 .../tests/unit/PutFileTests.cpp                    | 202 ++++++------
 .../tests/unit/RetryFlowFileTests.cpp              |  24 +-
 .../tests/unit/TailFileTests.cpp                   | 293 ++++++++----------
 extensions/windows-event-log/Bookmark.cpp          |  12 +-
 extensions/windows-event-log/Bookmark.h            |   4 +-
 extensions/windows-event-log/tests/CWELTestUtils.h |   4 +-
 libminifi/include/Defaults.h                       |  18 +-
 libminifi/include/controllers/SSLContextService.h  |  24 +-
 libminifi/include/core/ConfigurableComponent.h     |  12 +
 libminifi/include/core/ContentRepository.h         |  17 +-
 libminifi/include/core/FlowConfiguration.h         |  28 +-
 .../include/core/ProcessSessionReadCallback.h      |  10 +-
 libminifi/include/core/extension/Utils.h           |   8 +-
 .../include/core/logging/LoggerConfiguration.h     |  19 +-
 libminifi/include/core/yaml/YamlConfiguration.h    |   2 +-
 libminifi/include/io/FileStream.h                  |  27 +-
 libminifi/include/properties/Decryptor.h           |   2 +-
 libminifi/include/properties/Properties.h          |  28 +-
 libminifi/include/properties/PropertiesFile.h      |  13 +-
 libminifi/include/utils/ChecksumCalculator.h       |  23 +-
 libminifi/include/utils/Environment.h              |  29 +-
 libminifi/include/utils/FileReaderCallback.h       |   5 +-
 libminifi/include/utils/TestUtils.h                |  18 +-
 libminifi/include/utils/crypto/EncryptionManager.h |   5 +-
 .../include/utils/crypto/EncryptionProvider.h      |   3 +-
 libminifi/include/utils/file/FileManager.h         |  70 +----
 libminifi/include/utils/file/FilePattern.h         |  26 +-
 libminifi/include/utils/file/FileSystem.h          |   4 +-
 libminifi/include/utils/file/FileUtils.h           | 337 +++++----------------
 libminifi/include/utils/file/PathUtils.h           |  45 +--
 libminifi/include/utils/net/Ssl.h                  |   6 +-
 libminifi/include/utils/tls/CertificateUtils.h     |   4 +-
 libminifi/src/controllers/SSLContextService.cpp    | 124 ++++----
 libminifi/src/core/FlowConfiguration.cpp           |  21 +-
 libminifi/src/core/ProcessSessionReadCallback.cpp  |  40 +--
 libminifi/src/core/logging/LoggerConfiguration.cpp |  30 +-
 .../src/core/repository/FileSystemRepository.cpp   |   8 +-
 .../core/state/nodes/ConfigurationChecksums.cpp    |   2 +-
 libminifi/src/core/yaml/YamlConfiguration.cpp      |   2 +-
 libminifi/src/io/FileStream.cpp                    |  26 +-
 libminifi/src/properties/Properties.cpp            |  36 ++-
 libminifi/src/properties/PropertiesFile.cpp        |  14 +-
 libminifi/src/utils/ChecksumCalculator.cpp         |  24 +-
 libminifi/src/utils/Environment.cpp                |  77 +----
 libminifi/src/utils/FileReaderCallback.cpp         |  10 +-
 libminifi/src/utils/StringUtils.cpp                |   3 +-
 libminifi/src/utils/crypto/EncryptionManager.cpp   |  20 +-
 libminifi/src/utils/crypto/EncryptionProvider.cpp  |  17 +-
 libminifi/src/utils/file/FilePattern.cpp           |  52 ++--
 libminifi/src/utils/file/FileSystem.cpp            |  26 +-
 libminifi/src/utils/file/FileUtils.cpp             |   9 +-
 libminifi/src/utils/file/PathUtils.cpp             |  49 ---
 libminifi/src/utils/net/SslServer.cpp              |   6 +-
 libminifi/src/utils/tls/CertificateUtils.cpp       |   8 +-
 libminifi/test/Path.h                              |  69 -----
 libminifi/test/TestBase.cpp                        |   6 +-
 libminifi/test/TestBase.h                          |  10 +-
 libminifi/test/Utils.h                             |  20 +-
 .../test/archive-tests/CompressContentTests.cpp    |  40 +--
 libminifi/test/archive-tests/FocusArchiveTests.cpp |  20 +-
 .../test/archive-tests/ManipulateArchiveTests.cpp  |  12 +-
 libminifi/test/archive-tests/MergeFileTests.cpp    |  14 +-
 libminifi/test/archive-tests/util/ArchiveTests.cpp |  12 +-
 libminifi/test/aws-tests/FetchS3ObjectTests.cpp    |  11 +-
 libminifi/test/aws-tests/S3TestsFixture.h          |   8 +-
 .../azure-tests/AzureBlobStorageTestsFixture.h     |  18 +-
 .../azure-tests/AzureDataLakeStorageTestsFixture.h |  16 +-
 libminifi/test/flow-tests/SessionTests.cpp         |   4 +-
 libminifi/test/integration/IntegrationBase.h       |  10 +-
 .../PersistableKeyValueStoreServiceTest.cpp        |   9 +-
 .../UnorderedMapKeyValueStoreServiceTest.cpp       |   8 +-
 .../test/persistence-tests/PersistenceTests.cpp    |   8 +-
 .../test/rocksdb-tests/ContentSessionTests.cpp     |   4 +-
 .../rocksdb-tests/DBContentRepositoryTests.cpp     |  20 +-
 .../rocksdb-tests/DBProvenanceRepositoryTests.cpp  |   8 +-
 libminifi/test/rocksdb-tests/EncryptionTests.cpp   |  20 +-
 libminifi/test/rocksdb-tests/RepoTests.cpp         |  32 +-
 .../test/rocksdb-tests/RocksDBStreamTests.cpp      |   2 +-
 libminifi/test/rocksdb-tests/RocksDBTests.cpp      |  13 +-
 libminifi/test/rocksdb-tests/SwapTests.cpp         |   4 +-
 libminifi/test/sql-tests/SQLTestController.h       |   9 +-
 libminifi/test/unit/ChecksumCalculatorTests.cpp    |  28 +-
 .../test/unit/ConfigurationChecksumsTests.cpp      |  12 +-
 libminifi/test/unit/EnvironmentUtilsTests.cpp      |  59 ++--
 libminifi/test/unit/FilePatternTests.cpp           |   8 +-
 libminifi/test/unit/FileStreamTests.cpp            |  67 +---
 libminifi/test/unit/FileSystemRepositoryTests.cpp  |   2 +-
 libminifi/test/unit/FileSystemTests.cpp            |  14 +-
 libminifi/test/unit/FileTriggerTests.cpp           |  16 +-
 libminifi/test/unit/FileUtilsTests.cpp             | 222 +++-----------
 libminifi/test/unit/PathUtilsTests.cpp             |  18 --
 minifi_main/AgentDocs.cpp                          |  22 +-
 minifi_main/AgentDocs.h                            |   2 +-
 minifi_main/MainHelper.cpp                         |  73 ++---
 minifi_main/MainHelper.h                           |   3 +-
 minifi_main/MiNiFiMain.cpp                         |  20 +-
 nanofi/tests/CAPITests.cpp                         |  21 +-
 run_clang_tidy.sh                                  |  10 +-
 win_build_vs.bat                                   |   2 +-
 199 files changed, 2168 insertions(+), 3263 deletions(-)

diff --git a/CMakeSettings.json b/CMakeSettings.json
index f698d5237..544b82d5c 100644
--- a/CMakeSettings.json
+++ b/CMakeSettings.json
@@ -71,10 +71,6 @@
           "name": "ENABLE_SCRIPTING",
           "value": "OFF"
         },
-        {
-          "name": "EXCLUDE_BOOST",
-          "value": "ON"
-        },
         {
           "name": "ENABLE_WEL",
           "value": "TRUE"
diff --git a/Windows.md b/Windows.md
index 87be64da4..6ec23598e 100644
--- a/Windows.md
+++ b/Windows.md
@@ -99,7 +99,7 @@ A basic working CMake configuration can be inferred from the `win_build_vs.bat`.
 ```
 mkdir build
 cd build
-cmake -G "Visual Studio 16 2019" -DINSTALLER_MERGE_MODULES=OFF -DENABLE_SQL=OFF -DCMAKE_BUILD_TYPE_INIT=Release -DCMAKE_BUILD_TYPE=Release -DWIN32=WIN32 -DENABLE_LIBRDKAFKA=OFF -DENABLE_JNI=OFF -DOPENSSL_OFF=OFF -DENABLE_COAP=OFF -DUSE_SHARED_LIBS=OFF -DDISABLE_CONTROLLER=ON  -DBUILD_ROCKSDB=ON -DFORCE_WINDOWS=ON -DUSE_SYSTEM_UUID=OFF -DDISABLE_LIBARCHIVE=OFF -DDISABLE_SCRIPTING=ON -DEXCLUDE_BOOST=ON -DENABLE_WEL=TRUE -DFAIL_ON_WARNINGS=OFF -DSKIP_TESTS=OFF ..
+cmake -G "Visual Studio 16 2019" -DINSTALLER_MERGE_MODULES=OFF -DENABLE_SQL=OFF -DCMAKE_BUILD_TYPE_INIT=Release -DCMAKE_BUILD_TYPE=Release -DWIN32=WIN32 -DENABLE_LIBRDKAFKA=OFF -DENABLE_JNI=OFF -DOPENSSL_OFF=OFF -DENABLE_COAP=OFF -DUSE_SHARED_LIBS=OFF -DDISABLE_CONTROLLER=ON  -DBUILD_ROCKSDB=ON -DFORCE_WINDOWS=ON -DUSE_SYSTEM_UUID=OFF -DDISABLE_LIBARCHIVE=OFF -DDISABLE_SCRIPTING=ON -DENABLE_WEL=TRUE -DFAIL_ON_WARNINGS=OFF -DSKIP_TESTS=OFF ..
 msbuild /m nifi-minifi-cpp.sln /property:Configuration=Release /property:Platform=Win32
 copy minifi_main\Release\minifi.exe minifi_main\
 cpack
diff --git a/cmake/BuildTests.cmake b/cmake/BuildTests.cmake
index 62fc03ebf..bf21b589f 100644
--- a/cmake/BuildTests.cmake
+++ b/cmake/BuildTests.cmake
@@ -30,13 +30,6 @@ ENDMACRO()
 
 set(NANOFI_TEST_DIR "${CMAKE_SOURCE_DIR}/nanofi/tests/")
 
-if(NOT EXCLUDE_BOOST)
-    find_package(Boost COMPONENTS system filesystem)
-    if(Boost_FOUND)
-        add_definitions(-DUSE_BOOST)
-    endif()
-endif()
-
 function(appendIncludes testName)
     target_include_directories(${testName} SYSTEM BEFORE PRIVATE "${CMAKE_SOURCE_DIR}/thirdparty/catch")
     target_include_directories(${testName} BEFORE PRIVATE "${CMAKE_SOURCE_DIR}/include")
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 1eb88f757..58bf2a3a6 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -78,7 +78,7 @@ USER ${USER}
 ENV PATH /usr/lib/ccache/bin:${PATH}
 RUN mkdir ${MINIFI_BASE_DIR}/build
 WORKDIR ${MINIFI_BASE_DIR}/build
-RUN cmake -DSTATIC_BUILD= -DSKIP_TESTS=${DOCKER_SKIP_TESTS} ${MINIFI_OPTIONS} -DAWS_ENABLE_UNITY_BUILD=OFF -DEXCLUDE_BOOST=ON -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" .. && \
+RUN cmake -DSTATIC_BUILD= -DSKIP_TESTS=${DOCKER_SKIP_TESTS} ${MINIFI_OPTIONS} -DAWS_ENABLE_UNITY_BUILD=OFF -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" .. && \
     make -j "$(nproc)" package && \
     tar -xzvf "${MINIFI_BASE_DIR}/build/nifi-minifi-cpp-${MINIFI_VERSION}.tar.gz" -C "${MINIFI_BASE_DIR}"
 
diff --git a/docker/bionic/Dockerfile b/docker/bionic/Dockerfile
index 4bf1bb1f6..a715d2afa 100644
--- a/docker/bionic/Dockerfile
+++ b/docker/bionic/Dockerfile
@@ -49,5 +49,5 @@ ENV CXX g++-11
 RUN cd $MINIFI_BASE_DIR \
     && ./bootstrap.sh -t \
     && cd $MINIFI_BASE_DIR/build \
-    && cmake -DSTATIC_BUILD= -DSKIP_TESTS=true ${MINIFI_OPTIONS} -DAWS_ENABLE_UNITY_BUILD=OFF -DEXCLUDE_BOOST=ON -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" .. \
+    && cmake -DSTATIC_BUILD= -DSKIP_TESTS=true ${MINIFI_OPTIONS} -DAWS_ENABLE_UNITY_BUILD=OFF -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" .. \
     && make -j "$(nproc)" package
diff --git a/docker/centos/Dockerfile b/docker/centos/Dockerfile
index b98ea0352..e426acfda 100644
--- a/docker/centos/Dockerfile
+++ b/docker/centos/Dockerfile
@@ -59,5 +59,5 @@ RUN cd $MINIFI_BASE_DIR && \
     cd build && \
     source /opt/rh/devtoolset-10/enable && \
     export PATH=/usr/lib64/ccache${PATH:+:${PATH}} && \
-    cmake3 -DSTATIC_BUILD= -DSKIP_TESTS=${DOCKER_SKIP_TESTS} ${MINIFI_OPTIONS} -DAWS_ENABLE_UNITY_BUILD=OFF -DEXCLUDE_BOOST=ON -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" .. && \
+    cmake3 -DSTATIC_BUILD= -DSKIP_TESTS=${DOCKER_SKIP_TESTS} ${MINIFI_OPTIONS} -DAWS_ENABLE_UNITY_BUILD=OFF -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" .. && \
     make -j "$(nproc)" package
diff --git a/docker/fedora/Dockerfile b/docker/fedora/Dockerfile
index 17278299c..b6155d034 100644
--- a/docker/fedora/Dockerfile
+++ b/docker/fedora/Dockerfile
@@ -50,6 +50,6 @@ RUN cd $MINIFI_BASE_DIR \
 	&& rm -rf build \
 	&& mkdir build \
 	&& cd build \
-    && cmake3 -DSTATIC_BUILD= -DSKIP_TESTS=true ${MINIFI_OPTIONS} -DAWS_ENABLE_UNITY_BUILD=OFF -DEXCLUDE_BOOST=ON -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" .. \
+    && cmake3 -DSTATIC_BUILD= -DSKIP_TESTS=true ${MINIFI_OPTIONS} -DAWS_ENABLE_UNITY_BUILD=OFF -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" .. \
     && make -j "$(nproc)" package
 
diff --git a/docker/focal/Dockerfile b/docker/focal/Dockerfile
index a0220c10f..77b3ef74d 100644
--- a/docker/focal/Dockerfile
+++ b/docker/focal/Dockerfile
@@ -49,5 +49,5 @@ ENV CXX g++-11
 RUN cd $MINIFI_BASE_DIR \
     && ./bootstrap.sh -t \
     && cd $MINIFI_BASE_DIR/build \
-    && cmake -DSTATIC_BUILD= -DSKIP_TESTS=true ${MINIFI_OPTIONS} -DAWS_ENABLE_UNITY_BUILD=OFF -DEXCLUDE_BOOST=ON -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" .. \
+    && cmake -DSTATIC_BUILD= -DSKIP_TESTS=true ${MINIFI_OPTIONS} -DAWS_ENABLE_UNITY_BUILD=OFF -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" .. \
     && make -j "$(nproc)" package
diff --git a/encrypt-config/EncryptConfig.cpp b/encrypt-config/EncryptConfig.cpp
index 1bdbfa725..b212df669 100644
--- a/encrypt-config/EncryptConfig.cpp
+++ b/encrypt-config/EncryptConfig.cpp
@@ -35,11 +35,7 @@ constexpr const char* ENCRYPTION_KEY_PROPERTY_NAME = "nifi.bootstrap.sensitive.k
 
 }  // namespace
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace encrypt_config {
+namespace org::apache::nifi::minifi::encrypt_config {
 
 EncryptConfig::EncryptConfig(const std::string& minifi_home) : minifi_home_(minifi_home) {
   if (sodium_init() < 0) {
@@ -59,7 +55,7 @@ EncryptConfig::EncryptionType EncryptConfig::encryptSensitiveProperties() const
 
 void EncryptConfig::encryptFlowConfig() const {
   encrypt_config::ConfigFile properties_file{std::ifstream{propertiesFilePath()}};
-  std::optional<std::string> config_path = properties_file.getValue(Configure::nifi_flow_configuration_file);
+  std::optional<std::filesystem::path> config_path{properties_file.getValue(Configure::nifi_flow_configuration_file)};
   if (!config_path) {
     config_path = utils::file::PathUtils::resolve(minifi_home_, "conf/config.yml");
     std::cout << "Couldn't find path of configuration file, using default: \"" << *config_path << "\"\n";
@@ -73,7 +69,7 @@ void EncryptConfig::encryptFlowConfig() const {
     config_file.exceptions(std::ios::failbit | std::ios::badbit);
     config_content = std::string{std::istreambuf_iterator<char>(config_file), {}};
   } catch (...) {
-    throw std::runtime_error("Error while reading flow configuration file \"" + *config_path + "\"");
+    throw std::runtime_error("Error while reading flow configuration file \"" + config_path->string() + "\"");
   }
   try {
     utils::crypto::decrypt(config_content, keys_.encryption_key);
@@ -101,17 +97,17 @@ void EncryptConfig::encryptFlowConfig() const {
     encrypted_file.exceptions(std::ios::failbit | std::ios::badbit);
     encrypted_file << encrypted_content;
   } catch (...) {
-    throw std::runtime_error("Error while writing encrypted flow configuration file \"" + *config_path + "\"");
+    throw std::runtime_error("Error while writing encrypted flow configuration file \"" + config_path->string() + "\"");
   }
   std::cout << "Successfully encrypted flow configuration file: \"" << *config_path << "\"\n";
 }
 
-std::string EncryptConfig::bootstrapFilePath() const {
-  return utils::file::concat_path(minifi_home_, DEFAULT_BOOTSTRAP_FILE);
+std::filesystem::path EncryptConfig::bootstrapFilePath() const {
+  return minifi_home_ / DEFAULT_BOOTSTRAP_FILE;
 }
 
-std::string EncryptConfig::propertiesFilePath() const {
-  return utils::file::concat_path(minifi_home_, DEFAULT_NIFI_PROPERTIES_FILE);
+std::filesystem::path EncryptConfig::propertiesFilePath() const {
+  return minifi_home_ / DEFAULT_NIFI_PROPERTIES_FILE;
 }
 
 EncryptionKeys EncryptConfig::getEncryptionKeys() const {
@@ -170,7 +166,7 @@ void EncryptConfig::writeEncryptionKeyToBootstrapFile(const utils::crypto::Bytes
 void EncryptConfig::encryptSensitiveProperties(const EncryptionKeys& keys) const {
   encrypt_config::ConfigFile properties_file{std::ifstream{propertiesFilePath()}};
   if (properties_file.size() == 0) {
-    throw std::runtime_error{"Properties file " + propertiesFilePath() + " not found!"};
+    throw std::runtime_error{"Properties file " + propertiesFilePath().string() + " not found!"};
   }
 
   uint32_t num_properties_encrypted = encryptSensitivePropertiesInFile(properties_file, keys);
@@ -184,8 +180,4 @@ void EncryptConfig::encryptSensitiveProperties(const EncryptionKeys& keys) const
       << (num_properties_encrypted == 1 ? "property" : "properties") << " in " << propertiesFilePath() << '\n';
 }
 
-}  // namespace encrypt_config
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
+}  // namespace org::apache::nifi::minifi::encrypt_config
diff --git a/encrypt-config/EncryptConfig.h b/encrypt-config/EncryptConfig.h
index c89bed16c..6cdd6e440 100644
--- a/encrypt-config/EncryptConfig.h
+++ b/encrypt-config/EncryptConfig.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <string>
+#include <filesystem>
 
 #include "Utils.h"
 
@@ -39,8 +40,8 @@ class EncryptConfig {
   void encryptFlowConfig() const;
 
  private:
-  std::string bootstrapFilePath() const;
-  std::string propertiesFilePath() const;
+  std::filesystem::path bootstrapFilePath() const;
+  std::filesystem::path propertiesFilePath() const;
 
   EncryptionKeys getEncryptionKeys() const;
   std::string hexDecodeAndValidateKey(const std::string& key, const std::string& key_name) const;
@@ -48,7 +49,7 @@ class EncryptConfig {
 
   void encryptSensitiveProperties(const EncryptionKeys& keys) const;
 
-  const std::string minifi_home_;
+  const std::filesystem::path minifi_home_;
   EncryptionKeys keys_;
 };
 
diff --git a/encrypt-config/tests/ConfigFileTests.cpp b/encrypt-config/tests/ConfigFileTests.cpp
index e187b7edc..563524f72 100644
--- a/encrypt-config/tests/ConfigFileTests.cpp
+++ b/encrypt-config/tests/ConfigFileTests.cpp
@@ -167,9 +167,9 @@ TEST_CASE("ConfigFile can write to a new file", "[encrypt-config][writeTo]") {
   test_file.update(Configuration::nifi_bored_yield_duration, "20 millis");
 
   TestController test_controller;
-  std::string temp_dir = test_controller.createTempDirectory();
+  auto temp_dir = test_controller.createTempDirectory();
   auto remove_directory = minifi::gsl::finally([&temp_dir]() { utils::file::delete_dir(temp_dir); });
-  std::string file_path = utils::file::concat_path(temp_dir, "minifi.properties");
+  auto file_path = temp_dir / "minifi.properties";
 
   test_file.writeTo(file_path);
 
diff --git a/extensions/aws/processors/FetchS3Object.cpp b/extensions/aws/processors/FetchS3Object.cpp
index 10166f58d..835af992d 100644
--- a/extensions/aws/processors/FetchS3Object.cpp
+++ b/extensions/aws/processors/FetchS3Object.cpp
@@ -20,12 +20,10 @@
 
 #include "FetchS3Object.h"
 
-#include <set>
 #include <memory>
 
 #include "core/ProcessContext.h"
 #include "core/ProcessSession.h"
-#include "core/Resource.h"
 #include "utils/OptionalUtils.h"
 
 namespace org::apache::nifi::minifi::aws::processors {
@@ -99,9 +97,9 @@ void FetchS3Object::onTrigger(const std::shared_ptr<core::ProcessContext> &conte
 
     logger_->log_debug("Successfully fetched S3 object %s from bucket %s", get_object_params->object_key, get_object_params->bucket);
     session->putAttribute(flow_file, "s3.bucket", get_object_params->bucket);
-    session->putAttribute(flow_file, core::SpecialFlowAttribute::PATH, result->path);
-    session->putAttribute(flow_file, core::SpecialFlowAttribute::ABSOLUTE_PATH, result->absolute_path);
-    session->putAttribute(flow_file, core::SpecialFlowAttribute::FILENAME, result->filename);
+    session->putAttribute(flow_file, core::SpecialFlowAttribute::PATH, result->path.generic_string());
+    session->putAttribute(flow_file, core::SpecialFlowAttribute::ABSOLUTE_PATH, result->absolute_path.generic_string());
+    session->putAttribute(flow_file, core::SpecialFlowAttribute::FILENAME, result->filename.generic_string());
     putAttributeIfNotEmpty(core::SpecialFlowAttribute::MIME_TYPE, result->mime_type);
     putAttributeIfNotEmpty("s3.etag", result->etag);
     putAttributeIfNotEmpty("s3.expirationTime", result->expiration.expiration_time);
diff --git a/extensions/aws/s3/S3Wrapper.cpp b/extensions/aws/s3/S3Wrapper.cpp
index e8f573c8d..dab76976f 100644
--- a/extensions/aws/s3/S3Wrapper.cpp
+++ b/extensions/aws/s3/S3Wrapper.cpp
@@ -32,8 +32,9 @@
 namespace org::apache::nifi::minifi::aws::s3 {
 
 void HeadObjectResult::setFilePaths(const std::string& key) {
-  absolute_path = key;
-  std::tie(path, filename) = minifi::utils::file::split_path(key, true /*force_posix*/);
+  absolute_path = std::filesystem::path(key, std::filesystem::path::format::generic_format);
+  path = absolute_path.parent_path();
+  filename = absolute_path.filename();
 }
 
 S3Wrapper::S3Wrapper() : request_sender_(std::make_unique<S3ClientRequestSender>()) {
diff --git a/extensions/aws/s3/S3Wrapper.h b/extensions/aws/s3/S3Wrapper.h
index 676857f82..da37564fa 100644
--- a/extensions/aws/s3/S3Wrapper.h
+++ b/extensions/aws/s3/S3Wrapper.h
@@ -97,9 +97,9 @@ struct PutObjectResult {
 };
 
 struct RequestParameters {
-  RequestParameters(const Aws::Auth::AWSCredentials& creds, const Aws::Client::ClientConfiguration& config)
-    : credentials(creds)
-    , client_config(config) {}
+  RequestParameters(Aws::Auth::AWSCredentials creds, Aws::Client::ClientConfiguration  config)
+    : credentials(std::move(creds))
+    , client_config(std::move(config)) {}
   Aws::Auth::AWSCredentials credentials;
   Aws::Client::ClientConfiguration client_config;
 
@@ -146,9 +146,9 @@ struct GetObjectRequestParameters : public RequestParameters {
 };
 
 struct HeadObjectResult {
-  std::string path;
-  std::string absolute_path;
-  std::string filename;
+  std::filesystem::path path;
+  std::filesystem::path absolute_path;
+  std::filesystem::path filename;
   std::string mime_type;
   std::string etag;
   Expiration expiration;
@@ -174,11 +174,11 @@ struct ListRequestParameters : public RequestParameters {
 };
 
 struct ListedObjectAttributes : public minifi::utils::ListedObject {
-  std::chrono::time_point<std::chrono::system_clock> getLastModified() const override {
+  [[nodiscard]] std::chrono::time_point<std::chrono::system_clock> getLastModified() const override {
     return last_modified;
   }
 
-  std::string getKey() const override {
+  [[nodiscard]] std::string getKey() const override {
     return filename;
   }
 
@@ -212,7 +212,7 @@ class S3Wrapper {
   static Expiration getExpiration(const std::string& expiration);
 
   void setCannedAcl(Aws::S3::Model::PutObjectRequest& request, const std::string& canned_acl) const;
-  static int64_t writeFetchedBody(Aws::IOStream& source, const int64_t data_size, io::OutputStream& output);
+  static int64_t writeFetchedBody(Aws::IOStream& source, int64_t data_size, io::OutputStream& output);
   static std::string getEncryptionString(Aws::S3::Model::ServerSideEncryption encryption);
 
   std::optional<std::vector<ListedObjectAttributes>> listVersions(const ListRequestParameters& params);
diff --git a/extensions/azure/processors/ListAzureDataLakeStorage.cpp b/extensions/azure/processors/ListAzureDataLakeStorage.cpp
index 4dfc4e1bb..37de02cae 100644
--- a/extensions/azure/processors/ListAzureDataLakeStorage.cpp
+++ b/extensions/azure/processors/ListAzureDataLakeStorage.cpp
@@ -31,8 +31,8 @@ std::shared_ptr<core::FlowFile> createNewFlowFile(core::ProcessSession &session,
   auto flow_file = session.create();
   session.putAttribute(flow_file, "azure.filesystem", element.filesystem);
   session.putAttribute(flow_file, "azure.filePath", element.file_path);
-  session.putAttribute(flow_file, "azure.directory", element.directory);
-  session.putAttribute(flow_file, "azure.filename", element.filename);
+  session.putAttribute(flow_file, "azure.directory", element.directory.generic_string());
+  session.putAttribute(flow_file, "azure.filename", element.filename.generic_string());
   session.putAttribute(flow_file, "azure.length", std::to_string(element.length));
   session.putAttribute(flow_file, "azure.lastModified", std::to_string(element.last_modified.time_since_epoch() / std::chrono::milliseconds(1)));
   session.putAttribute(flow_file, "azure.etag", element.etag);
diff --git a/extensions/azure/storage/AzureDataLakeStorage.cpp b/extensions/azure/storage/AzureDataLakeStorage.cpp
index b53bc201a..9f575428c 100644
--- a/extensions/azure/storage/AzureDataLakeStorage.cpp
+++ b/extensions/azure/storage/AzureDataLakeStorage.cpp
@@ -112,12 +112,11 @@ std::optional<ListDataLakeStorageResult> AzureDataLakeStorage::listDirectory(con
         continue;
       }
       ListDataLakeStorageElement element;
-      auto [directory, filename] = minifi::utils::file::FileUtils::split_path(azure_element.Name, true /*force_posix*/);
-      if (!directory.empty()) {
-        directory = directory.substr(0, directory.size() - 1);  // Remove ending '/' character
-      }
+      auto path = std::filesystem::path(azure_element.Name, std::filesystem::path::format::generic_format);
+      auto directory = path.parent_path();
+      auto filename = path.filename();
 
-      if (!matchesPathFilter(params.directory_name, params.path_regex, directory) || !matchesFileFilter(params.file_regex, filename)) {
+      if (!matchesPathFilter(params.directory_name, params.path_regex, directory.generic_string()) || !matchesFileFilter(params.file_regex, filename.generic_string())) {
         continue;
       }
 
diff --git a/extensions/azure/storage/AzureDataLakeStorage.h b/extensions/azure/storage/AzureDataLakeStorage.h
index d7e05f7db..6e1756c0f 100644
--- a/extensions/azure/storage/AzureDataLakeStorage.h
+++ b/extensions/azure/storage/AzureDataLakeStorage.h
@@ -46,8 +46,8 @@ struct UploadDataLakeStorageResult {
 struct ListDataLakeStorageElement : public minifi::utils::ListedObject {
   std::string filesystem;
   std::string file_path;
-  std::string directory;
-  std::string filename;
+  std::filesystem::path directory;
+  std::filesystem::path filename;
   uint64_t length = 0;
   std::chrono::time_point<std::chrono::system_clock> last_modified;
   std::string etag;
diff --git a/extensions/civetweb/tests/ListenHTTPTests.cpp b/extensions/civetweb/tests/ListenHTTPTests.cpp
index e9d326e70..c14b434b3 100644
--- a/extensions/civetweb/tests/ListenHTTPTests.cpp
+++ b/extensions/civetweb/tests/ListenHTTPTests.cpp
@@ -70,9 +70,8 @@ class ListenHTTPTestsFixture {
     REQUIRE(!tmp_dir.empty());
 
     // Define test input file
-    std::string test_input_file = minifi::utils::file::FileUtils::concat_path(tmp_dir, "test");
     {
-      std::ofstream os(test_input_file);
+      std::ofstream os(tmp_dir / "test");
       os << "Hello response body";
     }
 
@@ -98,7 +97,7 @@ class ListenHTTPTestsFixture {
         true);
 
     // Configure GetFile processor
-    plan->setProperty(get_file, "Input Directory", tmp_dir);
+    plan->setProperty(get_file, "Input Directory", tmp_dir.string());
 
     // Configure UpdateAttribute processor
     plan->setProperty(update_attribute, "http.type", "response_body", true);
@@ -115,12 +114,13 @@ class ListenHTTPTestsFixture {
 
   void create_ssl_context_service(const char* ca, const char* client_cert) {
     auto config = std::make_shared<minifi::Configure>();
+    auto executable_dir = minifi::utils::file::FileUtils::get_executable_dir();
     if (ca != nullptr) {
-      config->set(minifi::Configure::nifi_security_client_ca_certificate, minifi::utils::file::FileUtils::get_executable_dir() + "/resources/" + ca);
+      config->set(minifi::Configure::nifi_security_client_ca_certificate, (executable_dir / "resources" / ca).string());
     }
     if (client_cert != nullptr) {
-      config->set(minifi::Configure::nifi_security_client_certificate, minifi::utils::file::FileUtils::get_executable_dir() + "/resources/" + client_cert);
-      config->set(minifi::Configure::nifi_security_client_private_key, minifi::utils::file::FileUtils::get_executable_dir() + "/resources/" + client_cert);
+      config->set(minifi::Configure::nifi_security_client_certificate, (executable_dir / "resources" / client_cert).string());
+      config->set(minifi::Configure::nifi_security_client_private_key, (executable_dir / "resources" / client_cert).string());
       config->set(minifi::Configure::nifi_security_client_pass_phrase, "Password12");
     }
     ssl_context_service = std::make_shared<minifi::controllers::SSLContextService>("SSLContextService", config);
@@ -219,7 +219,7 @@ class ListenHTTPTestsFixture {
   }
 
  protected:
-  std::string tmp_dir;
+  std::filesystem::path tmp_dir;
   TestController testController;
   std::shared_ptr<TestPlan> plan;
   std::shared_ptr<core::Processor> get_file;
@@ -425,7 +425,7 @@ TEST_CASE_METHOD(ListenHTTPTestsFixture, "HTTP Batch tests", "[batch]") {
 
 #ifdef OPENSSL_SUPPORT
 TEST_CASE_METHOD(ListenHTTPTestsFixture, "HTTPS without CA", "[basic][https]") {
-  plan->setProperty(listen_http, "SSL Certificate", minifi::utils::file::FileUtils::concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "resources/server.pem"));
+  plan->setProperty(listen_http, "SSL Certificate", (minifi::utils::file::FileUtils::get_executable_dir() / "resources" / "server.pem").string());
 
   create_ssl_context_service("goodCA.crt", nullptr);
 
@@ -446,8 +446,9 @@ TEST_CASE_METHOD(ListenHTTPTestsFixture, "HTTPS without CA", "[basic][https]") {
 }
 
 TEST_CASE_METHOD(ListenHTTPTestsFixture, "HTTPS without client cert", "[basic][https]") {
-  plan->setProperty(listen_http, "SSL Certificate", minifi::utils::file::FileUtils::concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "resources/server.pem"));
-  plan->setProperty(listen_http, "SSL Certificate Authority", minifi::utils::file::FileUtils::concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "resources/goodCA.crt"));
+  auto executable_dir = minifi::utils::file::FileUtils::get_executable_dir();
+  plan->setProperty(listen_http, "SSL Certificate", (executable_dir / "resources" / "server.pem").string());
+  plan->setProperty(listen_http, "SSL Certificate Authority", (executable_dir / "resources" / "goodCA.crt").string());
 
   create_ssl_context_service("goodCA.crt", nullptr);
 
@@ -468,8 +469,9 @@ TEST_CASE_METHOD(ListenHTTPTestsFixture, "HTTPS without client cert", "[basic][h
 }
 
 TEST_CASE_METHOD(ListenHTTPTestsFixture, "HTTPS with client cert from good CA", "[https]") {
-  plan->setProperty(listen_http, "SSL Certificate", minifi::utils::file::FileUtils::concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "resources/server.pem"));
-  plan->setProperty(listen_http, "SSL Certificate Authority", minifi::utils::file::FileUtils::concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "resources/goodCA.crt"));
+  auto executable_dir = minifi::utils::file::FileUtils::get_executable_dir();
+  plan->setProperty(listen_http, "SSL Certificate", (executable_dir / "resources" / "server.pem").string());
+  plan->setProperty(listen_http, "SSL Certificate Authority", (executable_dir / "resources" / "goodCA.crt").string());
   plan->setProperty(listen_http, "SSL Verify Peer", "yes");
 
   create_ssl_context_service("goodCA.crt", "goodCA_goodClient.pem");
@@ -491,8 +493,9 @@ TEST_CASE_METHOD(ListenHTTPTestsFixture, "HTTPS with client cert from good CA",
 }
 
 TEST_CASE_METHOD(ListenHTTPTestsFixture, "HTTPS with PKCS12 client cert from good CA", "[https]") {
-  plan->setProperty(listen_http, "SSL Certificate", minifi::utils::file::FileUtils::concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "resources/server.pem"));
-  plan->setProperty(listen_http, "SSL Certificate Authority", minifi::utils::file::FileUtils::concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "resources/goodCA.crt"));
+  auto executable_dir = minifi::utils::file::FileUtils::get_executable_dir();
+  plan->setProperty(listen_http, "SSL Certificate", (executable_dir / "resources" / "server.pem").string());
+  plan->setProperty(listen_http, "SSL Certificate Authority", (executable_dir / "resources" / "goodCA.crt").string());
   plan->setProperty(listen_http, "SSL Verify Peer", "yes");
 
   create_ssl_context_service("goodCA.crt", "goodCA_goodClient.p12");
@@ -514,8 +517,9 @@ TEST_CASE_METHOD(ListenHTTPTestsFixture, "HTTPS with PKCS12 client cert from goo
 }
 
 TEST_CASE_METHOD(ListenHTTPTestsFixture, "HTTPS with client cert from bad CA", "[https]") {
-  plan->setProperty(listen_http, "SSL Certificate", minifi::utils::file::FileUtils::concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "resources/server.pem"));
-  plan->setProperty(listen_http, "SSL Certificate Authority", minifi::utils::file::FileUtils::concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "resources/goodCA.crt"));
+  auto executable_dir = minifi::utils::file::FileUtils::get_executable_dir();
+  plan->setProperty(listen_http, "SSL Certificate", (executable_dir / "resources" / "server.pem").string());
+  plan->setProperty(listen_http, "SSL Certificate Authority", (executable_dir / "resources" / "goodCA.crt").string());
 
   bool should_succeed = false;
   int64_t response_code = 0;
@@ -560,8 +564,9 @@ TEST_CASE_METHOD(ListenHTTPTestsFixture, "HTTPS with client cert from bad CA", "
 }
 
 TEST_CASE_METHOD(ListenHTTPTestsFixture, "HTTPS with client cert with matching DN", "[https][DN]") {
-  plan->setProperty(listen_http, "SSL Certificate", minifi::utils::file::FileUtils::concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "resources/server.pem"));
-  plan->setProperty(listen_http, "SSL Certificate Authority", minifi::utils::file::FileUtils::concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "resources/goodCA.crt"));
+  auto executable_dir = minifi::utils::file::FileUtils::get_executable_dir();
+  plan->setProperty(listen_http, "SSL Certificate", (executable_dir / "resources" / "server.pem").string());
+  plan->setProperty(listen_http, "SSL Certificate Authority", (executable_dir / "resources" / "goodCA.crt").string());
   plan->setProperty(listen_http, "Authorized DN Pattern", ".*/CN=good\\..*");
   plan->setProperty(listen_http, "SSL Verify Peer", "yes");
 
@@ -584,8 +589,9 @@ TEST_CASE_METHOD(ListenHTTPTestsFixture, "HTTPS with client cert with matching D
 }
 
 TEST_CASE_METHOD(ListenHTTPTestsFixture, "HTTPS with client cert with non-matching DN", "[https][DN]") {
-  plan->setProperty(listen_http, "SSL Certificate", minifi::utils::file::FileUtils::concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "resources/server.pem"));
-  plan->setProperty(listen_http, "SSL Certificate Authority", minifi::utils::file::FileUtils::concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "resources/goodCA.crt"));
+  auto executable_dir = minifi::utils::file::FileUtils::get_executable_dir();
+  plan->setProperty(listen_http, "SSL Certificate", (executable_dir / "resources" / "server.pem").string());
+  plan->setProperty(listen_http, "SSL Certificate Authority", (executable_dir / "resources" / "goodCA.crt").string());
   plan->setProperty(listen_http, "Authorized DN Pattern", ".*/CN=good\\..*");
 
   int64_t response_code = 0;
@@ -630,8 +636,9 @@ TEST_CASE_METHOD(ListenHTTPTestsFixture, "HTTPS with client cert with non-matchi
 
 #if CURL_AT_LEAST_VERSION(7, 54, 0)
 TEST_CASE_METHOD(ListenHTTPTestsFixture, "HTTPS minimum SSL version", "[https]") {
-  plan->setProperty(listen_http, "SSL Certificate", minifi::utils::file::FileUtils::concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "resources/server.pem"));
-  plan->setProperty(listen_http, "SSL Certificate Authority", minifi::utils::file::FileUtils::concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "resources/goodCA.crt"));
+  auto executable_dir = minifi::utils::file::FileUtils::get_executable_dir();
+  plan->setProperty(listen_http, "SSL Certificate", (executable_dir / "resources" / "server.pem").string());
+  plan->setProperty(listen_http, "SSL Certificate Authority", (executable_dir / "resources" / "goodCA.crt").string());
 
   SECTION("GET") {
     method = "GET";
diff --git a/extensions/coap/tests/CoapIntegrationBase.h b/extensions/coap/tests/CoapIntegrationBase.h
index 7f62adf50..43e97d17d 100644
--- a/extensions/coap/tests/CoapIntegrationBase.h
+++ b/extensions/coap/tests/CoapIntegrationBase.h
@@ -50,14 +50,14 @@ class CoapIntegrationBase : public IntegrationBase {
     server.reset();
   }
 
-  void run(const std::optional<std::string>& test_file_location = {}, const std::optional<std::string>& = {}) override {
+  void run(const std::optional<std::filesystem::path>& test_file_location = {}, const std::optional<std::filesystem::path>& = {}) override {
     testSetup();
 
     std::shared_ptr<core::Repository> test_repo = std::make_shared<TestThreadedRepository>();
     std::shared_ptr<core::Repository> test_flow_repo = std::make_shared<TestFlowRepository>();
 
     if (test_file_location) {
-      configuration->set(minifi::Configure::nifi_flow_configuration_file, *test_file_location);
+      configuration->set(minifi::Configure::nifi_flow_configuration_file, test_file_location->string());
     }
     configuration->set(minifi::Configure::nifi_c2_agent_heartbeat_period, "200");
 
diff --git a/extensions/expression-language/tests/ExpressionLanguageTests.cpp b/extensions/expression-language/tests/ExpressionLanguageTests.cpp
index efb0f33ab..77cd383af 100644
--- a/extensions/expression-language/tests/ExpressionLanguageTests.cpp
+++ b/extensions/expression-language/tests/ExpressionLanguageTests.cpp
@@ -196,20 +196,18 @@ TEST_CASE("GetFile PutFile dynamic attribute", "[expressionLanguageTestGetFilePu
   auto plan = testController.createPlan(conf);
   auto repo = std::make_shared<TestRepository>();
 
-  std::string in_dir = testController.createTempDirectory();
+  auto in_dir = testController.createTempDirectory();
   REQUIRE(!in_dir.empty());
 
-  std::string in_file(in_dir);
-  in_file.append("/file");
-  std::string out_dir = testController.createTempDirectory();
+  auto in_file = in_dir / "file";
+  auto out_dir = testController.createTempDirectory();
   REQUIRE(!out_dir.empty());
 
-  std::string out_file(out_dir);
-  out_file.append("/extracted_attr/file");
+  auto out_file = out_dir / "extracted_attr" / "file";
 
   // Build MiNiFi processing graph
   auto get_file = plan->addProcessor("GetFile", "GetFile");
-  plan->setProperty(get_file, minifi::processors::GetFile::Directory.getName(), in_dir);
+  plan->setProperty(get_file, minifi::processors::GetFile::Directory.getName(), in_dir.string());
   plan->setProperty(get_file, minifi::processors::GetFile::KeepSourceFile.getName(), "false");
   auto update = plan->addProcessor("UpdateAttribute", "UpdateAttribute", core::Relationship("success", "description"), true);
   update->setDynamicProperty("prop_attr", "${'nifi.my.own.property'}_added");
@@ -218,7 +216,7 @@ TEST_CASE("GetFile PutFile dynamic attribute", "[expressionLanguageTestGetFilePu
   plan->setProperty(extract_text, minifi::processors::ExtractText::Attribute.getName(), "extracted_attr_name");
   plan->addProcessor("LogAttribute", "LogAttribute", core::Relationship("success", "description"), true);
   auto put_file = plan->addProcessor("PutFile", "PutFile", core::Relationship("success", "description"), true);
-  plan->setProperty(put_file, minifi::processors::PutFile::Directory.getName(), out_dir + "/${extracted_attr_name}");
+  plan->setProperty(put_file, minifi::processors::PutFile::Directory.getName(), (out_dir / "${extracted_attr_name}").string());
   plan->setProperty(put_file, minifi::processors::PutFile::ConflictResolution.getName(), minifi::processors::PutFile::CONFLICT_RESOLUTION_STRATEGY_REPLACE);
   plan->setProperty(put_file, minifi::processors::PutFile::CreateDirs.getName(), "true");
 
@@ -1272,14 +1270,14 @@ TEST_CASE("IP", "[expressionIP]") {
   auto expr = expression::compile("${ip()}");
 
   auto flow_file_a = std::make_shared<core::FlowFile>();
-  REQUIRE("" != expr(expression::Parameters{ flow_file_a }).asString());
+  REQUIRE(!expr(expression::Parameters{ flow_file_a }).asString().empty());
 }
 
 TEST_CASE("Full Hostname", "[expressionFullHostname]") {
   auto expr = expression::compile("${hostname('true')}");
 
   auto flow_file_a = std::make_shared<core::FlowFile>();
-  REQUIRE("" != expr(expression::Parameters{ flow_file_a }).asString());
+  REQUIRE(!expr(expression::Parameters{ flow_file_a }).asString().empty());
 }
 
 TEST_CASE("UUID", "[expressionUuid]") {
@@ -1557,7 +1555,6 @@ TEST_CASE("resolve_user_id_test", "[resolve_user_id tests]") {
 
   SECTION("TEST empty") {
   flow_file_a->addAttribute("attribute_sid", "");
-  REQUIRE("" == expr(expression::Parameters{flow_file_a}).asString());
+  REQUIRE(expr(expression::Parameters{flow_file_a}).asString().empty());
 }
 }
-
diff --git a/extensions/http-curl/client/HTTPClient.cpp b/extensions/http-curl/client/HTTPClient.cpp
index 9fb7d2caa..21a1d037e 100644
--- a/extensions/http-curl/client/HTTPClient.cpp
+++ b/extensions/http-curl/client/HTTPClient.cpp
@@ -409,9 +409,9 @@ int HTTPClient::onProgress(void *clientp, curl_off_t /*dltotal*/, curl_off_t dln
 void HTTPClient::configure_secure_connection() {
 #ifdef OPENSSL_SUPPORT
   if (ssl_context_service_) {
-    logger_->log_debug("Using certificate file \"%s\"", ssl_context_service_->getCertificateFile());
-    logger_->log_debug("Using private key file \"%s\"", ssl_context_service_->getPrivateKeyFile());
-    logger_->log_debug("Using CA certificate file \"%s\"", ssl_context_service_->getCACertificate());
+    logger_->log_debug("Using certificate file \"%s\"", ssl_context_service_->getCertificateFile().string());
+    logger_->log_debug("Using private key file \"%s\"", ssl_context_service_->getPrivateKeyFile().string());
+    logger_->log_debug("Using CA certificate file \"%s\"", ssl_context_service_->getCACertificate().string());
 
     curl_easy_setopt(http_session_.get(), CURLOPT_SSL_CTX_FUNCTION, &configure_ssl_context);
     curl_easy_setopt(http_session_.get(), CURLOPT_SSL_CTX_DATA, static_cast<void *>(ssl_context_service_.get()));
diff --git a/extensions/http-curl/tests/C2ClearCoreComponentStateTest.cpp b/extensions/http-curl/tests/C2ClearCoreComponentStateTest.cpp
index ece5a8798..ebdfd3e20 100644
--- a/extensions/http-curl/tests/C2ClearCoreComponentStateTest.cpp
+++ b/extensions/http-curl/tests/C2ClearCoreComponentStateTest.cpp
@@ -51,15 +51,15 @@ class VerifyC2ClearCoreComponentState : public VerifyC2Base {
     assert(verifyEventHappenedInPollTime(40s, [&] { return component_cleared_successfully_.load(); }, 1s));
   }
 
-  [[nodiscard]] std::string getFile1Location() const {
+  [[nodiscard]] std::filesystem::path getFile1Location() const {
     return test_file_1_;
   }
 
  protected:
   void updateProperties(minifi::FlowController& flow_controller) override {
-    auto setFileName = [] (const std::string& fileName, minifi::state::StateController& component){
+    auto setFileName = [] (const std::filesystem::path& fileName, minifi::state::StateController& component){
       auto& processor = dynamic_cast<minifi::state::ProcessorController&>(component).getProcessor();
-      processor.setProperty(minifi::processors::TailFile::FileName, fileName);
+      processor.setProperty(minifi::processors::TailFile::FileName, fileName.string());
     };
 
     flow_controller.executeOnComponent("TailFile1",
@@ -69,8 +69,8 @@ class VerifyC2ClearCoreComponentState : public VerifyC2Base {
   }
 
   TestController testController;
-  std::string test_file_1_;
-  std::string test_file_2_;
+  std::filesystem::path test_file_1_;
+  std::filesystem::path test_file_2_;
   const std::atomic_bool& component_cleared_successfully_;
 };
 
@@ -78,7 +78,7 @@ class ClearCoreComponentStateHandler: public HeartbeatHandler {
  public:
   explicit ClearCoreComponentStateHandler(std::atomic_bool& component_cleared_successfully,
                                           std::shared_ptr<minifi::Configure> configuration,
-                                          std::string file1Location)
+                                          std::filesystem::path file1Location)
     : HeartbeatHandler(std::move(configuration)),
       component_cleared_successfully_(component_cleared_successfully),
       file_1_location_(std::move(file1Location)) {
@@ -138,7 +138,7 @@ class ClearCoreComponentStateHandler: public HeartbeatHandler {
       case FlowState::CLEAR_SENT: {
         auto tail_file_ran_again_checker = [this] {
           const auto log_contents = LogTestController::getInstance().log_output.str();
-          const std::string tailing_file_pattern = "[debug] Tailing file " + file_1_location_;
+          const std::string tailing_file_pattern = "[debug] Tailing file " + file_1_location_.string();
           const std::string tail_file_committed_pattern = "[trace] ProcessSession committed for TailFile1";
           const std::vector<std::string> patterns = {tailing_file_pattern, tailing_file_pattern, tail_file_committed_pattern};
           return utils::StringUtils::matchesSequence(log_contents, patterns);
@@ -187,7 +187,7 @@ class ClearCoreComponentStateHandler: public HeartbeatHandler {
   std::atomic_bool& component_cleared_successfully_;
   std::string last_read_time_1_;
   std::string last_read_time_2_;
-  std::string file_1_location_;
+  std::filesystem::path file_1_location_;
 };
 
 int main(int argc, char **argv) {
diff --git a/extensions/http-curl/tests/C2ConfigEncryption.cpp b/extensions/http-curl/tests/C2ConfigEncryption.cpp
index 90552f614..915742757 100644
--- a/extensions/http-curl/tests/C2ConfigEncryption.cpp
+++ b/extensions/http-curl/tests/C2ConfigEncryption.cpp
@@ -30,8 +30,8 @@ int main(int argc, char **argv) {
   const cmd_args args = parse_cmdline_args(argc, argv, "update");
   TestController controller;
   // copy config file to temporary location as it will get overridden
-  std::string home_path = controller.createTempDirectory();
-  std::string live_config_file = utils::file::concat_path(home_path, "config.yml");
+  auto home_path = controller.createTempDirectory();
+  auto live_config_file = home_path / "config.yml";
   utils::file::copy_file(args.test_file, live_config_file);
   // the C2 server will update the flow with the contents of args.test_file
   // which will be encrypted and persisted to the temporary live_config_file
diff --git a/extensions/http-curl/tests/C2DescribeCoreComponentStateTest.cpp b/extensions/http-curl/tests/C2DescribeCoreComponentStateTest.cpp
index 1439df089..ea88afed0 100644
--- a/extensions/http-curl/tests/C2DescribeCoreComponentStateTest.cpp
+++ b/extensions/http-curl/tests/C2DescribeCoreComponentStateTest.cpp
@@ -32,8 +32,8 @@ class VerifyC2DescribeCoreComponentState : public VerifyC2Describe {
     : VerifyC2Describe(verified) {
     temp_dir_ = testController.createTempDirectory();
 
-    test_file_1_ = utils::file::FileUtils::concat_path(temp_dir_, "test1.txt");
-    test_file_2_ = utils::file::FileUtils::concat_path(temp_dir_, "test2.txt");
+    test_file_1_ = temp_dir_ / "test1.txt";
+    test_file_2_ = temp_dir_ / "test2.txt";
 
     std::ofstream f1(test_file_1_, std::ios::out | std::ios::binary);
     f1 << "foo\n";
@@ -44,9 +44,9 @@ class VerifyC2DescribeCoreComponentState : public VerifyC2Describe {
 
  protected:
   void updateProperties(minifi::FlowController& flow_controller) override {
-    auto setFileName = [] (const std::string& fileName, minifi::state::StateController& component){
+    auto setFileName = [] (const std::filesystem::path& file_name, minifi::state::StateController& component){
       auto& processor = dynamic_cast<minifi::state::ProcessorController&>(component).getProcessor();
-      processor.setProperty(minifi::processors::TailFile::FileName, fileName);
+      processor.setProperty(minifi::processors::TailFile::FileName, file_name.string());
     };
 
     flow_controller.executeOnComponent("TailFile1",
@@ -56,9 +56,9 @@ class VerifyC2DescribeCoreComponentState : public VerifyC2Describe {
   }
 
   TestController testController;
-  std::string temp_dir_;
-  std::string test_file_1_;
-  std::string test_file_2_;
+  std::filesystem::path temp_dir_;
+  std::filesystem::path test_file_1_;
+  std::filesystem::path test_file_2_;
 };
 
 class DescribeCoreComponentStateHandler: public HeartbeatHandler {
diff --git a/extensions/http-curl/tests/C2FetchFlowIfMissingTest.cpp b/extensions/http-curl/tests/C2FetchFlowIfMissingTest.cpp
index 63a52ab63..9fb1236a6 100644
--- a/extensions/http-curl/tests/C2FetchFlowIfMissingTest.cpp
+++ b/extensions/http-curl/tests/C2FetchFlowIfMissingTest.cpp
@@ -26,7 +26,7 @@ using namespace std::literals::chrono_literals;
 
 int main(int argc, char **argv) {
   TestController controller;
-  std::string minifi_home = controller.createTempDirectory();
+  auto minifi_home = controller.createTempDirectory();
   const cmd_args args = parse_cmdline_args(argc, argv);
   C2FlowProvider handler(args.test_file);
   VerifyFlowFetched harness(10s);
@@ -34,12 +34,10 @@ int main(int argc, char **argv) {
   harness.setUrl(args.url, &handler);
   harness.setFlowUrl(harness.getC2RestUrl());
 
-  std::string config_path = utils::file::PathUtils::concat_path(minifi_home, "config.yml");
-
-  harness.run(config_path);
+  harness.run(minifi_home / "config.yml");
 
   // check existence of the config file
-  assert(std::ifstream{config_path});
+  assert(std::ifstream{minifi_home / "config.yml"});
 
   return 0;
 }
diff --git a/extensions/http-curl/tests/C2MetricsTest.cpp b/extensions/http-curl/tests/C2MetricsTest.cpp
index 0da989316..16dd9fcbc 100644
--- a/extensions/http-curl/tests/C2MetricsTest.cpp
+++ b/extensions/http-curl/tests/C2MetricsTest.cpp
@@ -61,10 +61,10 @@ class VerifyC2Metrics : public VerifyC2Base {
 
 class MetricsHandler: public HeartbeatHandler {
  public:
-  explicit MetricsHandler(std::atomic_bool& metrics_updated_successfully, std::shared_ptr<minifi::Configure> configuration, const std::string& replacement_config_path)
+  explicit MetricsHandler(std::atomic_bool& metrics_updated_successfully, std::shared_ptr<minifi::Configure> configuration, const std::filesystem::path& replacement_config_path)
     : HeartbeatHandler(std::move(configuration)),
       metrics_updated_successfully_(metrics_updated_successfully),
-      replacement_config_(getReplacementConfigAsJsonValue(replacement_config_path)) {
+      replacement_config_(getReplacementConfigAsJsonValue(replacement_config_path.string())) {
   }
 
   void handleHeartbeat(const rapidjson::Document& root, struct mg_connection* conn) override {
@@ -211,7 +211,6 @@ int main(int argc, char **argv) {
   harness.setKeyDir(args.key_dir);
   auto replacement_path = args.test_file;
   minifi::utils::StringUtils::replaceAll(replacement_path, "TestC2Metrics", "TestC2MetricsUpdate");
-  minifi::utils::StringUtils::replaceAll(replacement_path, "/", std::string(1, org::apache::nifi::minifi::utils::file::FileUtils::get_separator()));
   org::apache::nifi::minifi::test::MetricsHandler handler(metrics_updated_successfully, harness.getConfiguration(), replacement_path);
   harness.setUrl(args.url, &handler);
   harness.run(args.test_file);
diff --git a/extensions/http-curl/tests/C2NullConfiguration.cpp b/extensions/http-curl/tests/C2NullConfiguration.cpp
index 0706ece9c..4bbbdf670 100644
--- a/extensions/http-curl/tests/C2NullConfiguration.cpp
+++ b/extensions/http-curl/tests/C2NullConfiguration.cpp
@@ -50,8 +50,8 @@ class VerifyC2Server : public HTTPIntegrationBase {
     LogTestController::getInstance().setDebug<processors::LogAttribute>();
     LogTestController::getInstance().setDebug<core::ProcessSession>();
     std::fstream file;
-    ss << dir << "/" << "tstFile.ext";
-    file.open(ss.str(), std::ios::out);
+    auto path = dir / "tstFile.ext";
+    file.open(path, std::ios::out);
     file << "tempFile";
     file.close();
   }
@@ -88,8 +88,7 @@ class VerifyC2Server : public HTTPIntegrationBase {
 
  protected:
   bool isSecure;
-  std::string dir;
-  std::stringstream ss;
+  std::filesystem::path dir;
   TestController testController;
 };
 
diff --git a/extensions/http-curl/tests/C2UpdateAssetTest.cpp b/extensions/http-curl/tests/C2UpdateAssetTest.cpp
index f1e0136b6..9c606c989 100644
--- a/extensions/http-curl/tests/C2UpdateAssetTest.cpp
+++ b/extensions/http-curl/tests/C2UpdateAssetTest.cpp
@@ -102,7 +102,7 @@ int main() {
   // setup minifi home
   const std::filesystem::path home_dir = controller.createTempDirectory();
   const auto asset_dir = home_dir / "asset";
-  utils::Environment::setCurrentWorkingDirectory(home_dir.string().c_str());
+  std::filesystem::current_path(home_dir);
 
   C2AcknowledgeHandler ack_handler;
   std::string file_A = "hello from file A";
diff --git a/extensions/http-curl/tests/C2VerifyServeResults.cpp b/extensions/http-curl/tests/C2VerifyServeResults.cpp
index 1d64bb4c5..acc0ff0e3 100644
--- a/extensions/http-curl/tests/C2VerifyServeResults.cpp
+++ b/extensions/http-curl/tests/C2VerifyServeResults.cpp
@@ -34,21 +34,21 @@
 class VerifyC2Server : public HTTPIntegrationBase {
  public:
   VerifyC2Server() {
-    dir = testController.createTempDirectory();
+    dir_ = testController.createTempDirectory();
   }
 
   void testSetup() override {
     LogTestController::getInstance().setDebug<minifi::processors::InvokeHTTP>();
     LogTestController::getInstance().setDebug<minifi::core::ProcessSession>();
     std::fstream file;
-    ss << dir << "/" << "tstFile.ext";
-    file.open(ss.str(), std::ios::out);
+    test_file_ = dir_ / "tstFile.ext";
+    file.open(test_file_, std::ios::out);
     file << "tempFile";
     file.close();
   }
 
   void cleanup() override {
-    std::remove(ss.str().c_str());
+    std::filesystem::remove(test_file_);
     IntegrationBase::cleanup();
   }
 
@@ -82,8 +82,8 @@ class VerifyC2Server : public HTTPIntegrationBase {
   }
 
  protected:
-  std::string dir;
-  std::stringstream ss;
+  std::filesystem::path dir_;
+  std::filesystem::path test_file_;
   TestController testController;
 };
 
diff --git a/extensions/http-curl/tests/ControllerServiceIntegrationTests.cpp b/extensions/http-curl/tests/ControllerServiceIntegrationTests.cpp
index a89a5cf61..ff6dd5eb3 100644
--- a/extensions/http-curl/tests/ControllerServiceIntegrationTests.cpp
+++ b/extensions/http-curl/tests/ControllerServiceIntegrationTests.cpp
@@ -105,7 +105,7 @@ int main(int argc, char **argv) {
     assert(ssl_client_cont->getControllerServiceImplementation() != nullptr);
     ssl_client = std::static_pointer_cast<minifi::controllers::SSLContextService>(ssl_client_cont->getControllerServiceImplementation());
   }
-  assert(ssl_client->getCACertificate().length() > 0);
+  assert(!ssl_client->getCACertificate().empty());
   // now let's disable one of the controller services.
   std::shared_ptr<core::controller::ControllerServiceNode> cs_id = controller->getControllerServiceNode("ID");
   const auto checkCsIdEnabledMatchesDisabledFlag = [&cs_id] { return !disabled == cs_id->enabled(); };
diff --git a/extensions/http-curl/tests/HTTPSiteToSiteTests.cpp b/extensions/http-curl/tests/HTTPSiteToSiteTests.cpp
index 247ad4c16..78ca8ac5f 100644
--- a/extensions/http-curl/tests/HTTPSiteToSiteTests.cpp
+++ b/extensions/http-curl/tests/HTTPSiteToSiteTests.cpp
@@ -56,8 +56,7 @@ class SiteToSiteTestHarness : public HTTPIntegrationBase {
     LogTestController::getInstance().setTrace<minifi::extensions::curl::HttpStreamingCallback>();
 
     std::fstream file;
-    ss << dir << utils::file::get_separator() << "tstFile.ext";
-    file.open(ss.str(), std::ios::out);
+    file.open(dir / "tstFile.ext", std::ios::out);
     file << "tempFile";
     file.close();
 
@@ -71,8 +70,7 @@ class SiteToSiteTestHarness : public HTTPIntegrationBase {
 
  protected:
   bool isSecure;
-  std::string dir;
-  std::stringstream ss;
+  std::filesystem::path dir;
   TestController testController;
 };
 
diff --git a/extensions/http-curl/tests/HttpPostIntegrationTest.cpp b/extensions/http-curl/tests/HttpPostIntegrationTest.cpp
index e934a12a6..d4271a3e9 100644
--- a/extensions/http-curl/tests/HttpPostIntegrationTest.cpp
+++ b/extensions/http-curl/tests/HttpPostIntegrationTest.cpp
@@ -38,7 +38,7 @@ using namespace std::literals::chrono_literals;
 class HttpTestHarness : public HTTPIntegrationBase {
  public:
   HttpTestHarness() : HTTPIntegrationBase(4s) {
-    dir = testController.createTempDirectory();
+    dir_ = test_controller_.createTempDirectory();
   }
 
   void testSetup() override {
@@ -56,8 +56,8 @@ class HttpTestHarness : public HTTPIntegrationBase {
     LogTestController::getInstance().setDebug<minifi::TimerDrivenSchedulingAgent>();
     LogTestController::getInstance().setDebug<minifi::core::ProcessSession>();
     std::fstream file;
-    ss << dir << "/" << "tstFile.ext";
-    file.open(ss.str(), std::ios::out);
+    test_file_ = dir_ / "tstFile.ext";
+    file.open(test_file_, std::ios::out);
     file << "tempFile";
     file.close();
     configuration->set(org::apache::nifi::minifi::Configuration::nifi_flow_engine_threads, "8");
@@ -65,7 +65,7 @@ class HttpTestHarness : public HTTPIntegrationBase {
   }
 
   void cleanup() override {
-    std::remove(ss.str().c_str());
+    std::filesystem::remove(test_file_);
     IntegrationBase::cleanup();
   }
 
@@ -78,9 +78,9 @@ class HttpTestHarness : public HTTPIntegrationBase {
   }
 
  protected:
-  std::string dir;
-  std::stringstream ss;
-  TestController testController;
+  std::filesystem::path dir_;
+  std::filesystem::path test_file_;
+  TestController test_controller_;
 };
 
 int main(int argc, char **argv) {
diff --git a/extensions/http-curl/tests/SiteToSiteRestTest.cpp b/extensions/http-curl/tests/SiteToSiteRestTest.cpp
index 77870e097..51c2ca7d4 100644
--- a/extensions/http-curl/tests/SiteToSiteRestTest.cpp
+++ b/extensions/http-curl/tests/SiteToSiteRestTest.cpp
@@ -66,7 +66,7 @@ class SiteToSiteTestHarness : public HTTPIntegrationBase {
  public:
   explicit SiteToSiteTestHarness(bool isSecure)
       : isSecure(isSecure) {
-    dir = testController.createTempDirectory();
+    dir_ = test_controller_.createTempDirectory();
   }
 
   void testSetup() override {
@@ -77,14 +77,14 @@ class SiteToSiteTestHarness : public HTTPIntegrationBase {
     LogTestController::getInstance().setDebug<core::ConfigurableComponent>();
 
     std::fstream file;
-    ss << dir << "/" << "tstFile.ext";
-    file.open(ss.str(), std::ios::out);
+    test_file_ = dir_ / "tstFile.ext";
+    file.open(test_file_, std::ios::out);
     file << "tempFile";
     file.close();
   }
 
   void cleanup() override {
-    std::remove(ss.str().c_str());
+    std::filesystem::remove(test_file_);
     IntegrationBase::cleanup();
   }
 
@@ -100,9 +100,9 @@ class SiteToSiteTestHarness : public HTTPIntegrationBase {
 
  protected:
   bool isSecure;
-  std::string dir;
-  std::stringstream ss;
-  TestController testController;
+  std::filesystem::path dir_;
+  std::filesystem::path test_file_;
+  TestController test_controller_;
 };
 
 int main(int argc, char **argv) {
diff --git a/extensions/http-curl/tests/TimeoutHTTPSiteToSiteTests.cpp b/extensions/http-curl/tests/TimeoutHTTPSiteToSiteTests.cpp
index 7d9fb4bf7..40bbb4893 100644
--- a/extensions/http-curl/tests/TimeoutHTTPSiteToSiteTests.cpp
+++ b/extensions/http-curl/tests/TimeoutHTTPSiteToSiteTests.cpp
@@ -58,8 +58,7 @@ class SiteToSiteTestHarness : public HTTPIntegrationBase {
     LogTestController::getInstance().setTrace<minifi::extensions::curl::HttpStreamingCallback>();
 
     std::fstream file;
-    ss << dir << utils::file::FileUtils::get_separator() << "tstFile.ext";
-    file.open(ss.str(), std::ios::out);
+    file.open(dir / "tstFile.ext", std::ios::out);
     file << "tempFile";
     file.close();
 
@@ -73,8 +72,7 @@ class SiteToSiteTestHarness : public HTTPIntegrationBase {
 
  protected:
   bool isSecure;
-  std::string dir;
-  std::stringstream ss;
+  std::filesystem::path dir;
   TestController testController;
 };
 
diff --git a/extensions/http-curl/tests/VerifyInvokeHTTP.h b/extensions/http-curl/tests/VerifyInvokeHTTP.h
index a4ce0196d..860bfd93a 100644
--- a/extensions/http-curl/tests/VerifyInvokeHTTP.h
+++ b/extensions/http-curl/tests/VerifyInvokeHTTP.h
@@ -78,14 +78,14 @@ class VerifyInvokeHTTP : public HTTPIntegrationBase {
     assert(executed);
   }
 
-  virtual void setupFlow(const std::optional<std::string>& flow_yml_path) {
+  virtual void setupFlow(const std::optional<std::filesystem::path>& flow_yml_path) {
     testSetup();
 
     std::shared_ptr<core::Repository> test_repo = std::make_shared<TestThreadedRepository>();
     std::shared_ptr<core::Repository> test_flow_repo = std::make_shared<TestFlowRepository>();
 
     if (flow_yml_path) {
-      configuration->set(minifi::Configure::nifi_flow_configuration_file, *flow_yml_path);
+      configuration->set(minifi::Configure::nifi_flow_configuration_file, flow_yml_path->string());
     }
     configuration->set(minifi::Configure::nifi_c2_agent_heartbeat_period, "200");
     std::shared_ptr<core::ContentRepository> content_repo = std::make_shared<core::repository::VolatileContentRepository>();
@@ -99,7 +99,7 @@ class VerifyInvokeHTTP : public HTTPIntegrationBase {
     setProperty(minifi::processors::InvokeHTTP::URL.getName(), url);
   }
 
-  void run(const std::optional<std::string>& flow_yml_path = {}, const std::optional<std::string>& = {}) override {
+  void run(const std::optional<std::filesystem::path>& flow_yml_path = {}, const std::optional<std::filesystem::path>& = {}) override {
     setupFlow(flow_yml_path);
     startFlowController();
 
diff --git a/extensions/http-curl/tests/VerifyInvokeHTTPPostTest.cpp b/extensions/http-curl/tests/VerifyInvokeHTTPPostTest.cpp
index a1630aff7..c6c19f72d 100644
--- a/extensions/http-curl/tests/VerifyInvokeHTTPPostTest.cpp
+++ b/extensions/http-curl/tests/VerifyInvokeHTTPPostTest.cpp
@@ -44,7 +44,7 @@ class VerifyInvokeHTTPOK200Response : public VerifyInvokeHTTP {
 
 class VerifyInvokeHTTPRedirectResponse : public VerifyInvokeHTTP {
  public:
-  void setupFlow(const std::optional<std::string>& flow_yml_path) override {
+  void setupFlow(const std::optional<std::filesystem::path>& flow_yml_path) override {
     VerifyInvokeHTTP::setupFlow(flow_yml_path);
     setProperty(minifi::processors::InvokeHTTP::FollowRedirects.getName(), "false");
   }
diff --git a/extensions/jni/JVMCreator.h b/extensions/jni/JVMCreator.h
index a634920b4..c88f17e19 100644
--- a/extensions/jni/JVMCreator.h
+++ b/extensions/jni/JVMCreator.h
@@ -95,7 +95,7 @@ class JVMCreator : public minifi::core::CoreComponent {
 
   std::vector<std::string> jvm_options_;
 
-  std::vector<std::string> classpaths_;
+  std::vector<std::filesystem::path> classpaths_;
 
   std::shared_ptr<core::logging::Logger> logger_ = core::logging::LoggerFactory<JVMCreator>::getLogger();
 };
diff --git a/extensions/jni/jvm/JVMLoader.h b/extensions/jni/jvm/JVMLoader.h
index cbe02e945..3e2d49c23 100644
--- a/extensions/jni/jvm/JVMLoader.h
+++ b/extensions/jni/jvm/JVMLoader.h
@@ -36,11 +36,7 @@
 #endif
 #include "Core.h"
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace jni {
+namespace org::apache::nifi::minifi::jni {
 
 /**
  * Purpose and Justification: Provides a mapping function for jfields.
@@ -103,8 +99,8 @@ class JVMLoader {
     jint ret = jvm_->GetEnv(reinterpret_cast<void**>(&jenv), JNI_VERSION_1_8);
 
     if (ret == JNI_EDETACHED) {
-      ret = jvm_->AttachCurrentThread(reinterpret_cast<void**>(&jenv), NULL);
-      if (ret != JNI_OK || jenv == NULL) {
+      ret = jvm_->AttachCurrentThread(reinterpret_cast<void**>(&jenv), nullptr);
+      if (ret != JNI_OK || jenv == nullptr) {
         throw std::runtime_error("Could not find class");
       }
     }
@@ -209,7 +205,7 @@ class JVMLoader {
    * @param pathVector vector of paths
    * @param otherOptions jvm options.
    */
-  static JVMLoader *getInstance(const std::vector<std::string> &pathVector, const std::vector<std::string> &otherOptions = std::vector<std::string>()) {
+  static JVMLoader *getInstance(const std::vector<std::filesystem::path> &pathVector, const std::vector<std::string> &otherOptions = std::vector<std::string>()) {
     JVMLoader *jvm = getInstance();
     if (!jvm->initialized()) {
       std::stringstream str;
@@ -505,8 +501,4 @@ class JVMLoader {
   }
 };
 
-} /* namespace jni */
-} /* namespace minifi */
-} /* namespace nifi */
-} /* namespace apache */
-} /* namespace org */
+}  // namespace org::apache::nifi::minifi::jni
diff --git a/extensions/jni/jvm/JavaControllerService.h b/extensions/jni/jvm/JavaControllerService.h
index 21f31cd02..7b7ebf547 100644
--- a/extensions/jni/jvm/JavaControllerService.h
+++ b/extensions/jni/jvm/JavaControllerService.h
@@ -81,7 +81,7 @@ class JavaControllerService : public core::controller::ControllerService, public
 
   void onEnable() override;
 
-  std::vector<std::string> getPaths() const {
+  std::vector<std::filesystem::path> getPaths() const {
     return classpaths_;
   }
 
@@ -149,7 +149,7 @@ class JavaControllerService : public core::controller::ControllerService, public
 
   bool initialized_ = false;
 
-  std::vector<std::string> classpaths_;
+  std::vector<std::filesystem::path> classpaths_;
 
   std::unique_ptr<NarClassLoader> nar_loader_;
 
diff --git a/extensions/jni/jvm/JniReferenceObjects.h b/extensions/jni/jvm/JniReferenceObjects.h
index 1d16a1f81..558f68561 100644
--- a/extensions/jni/jvm/JniReferenceObjects.h
+++ b/extensions/jni/jvm/JniReferenceObjects.h
@@ -34,12 +34,10 @@
 #include "core/ProcessSession.h"
 #include "core/WeakReference.h"
 #include "utils/gsl.h"
+#include "range/v3/algorithm/remove_if.hpp"
+#include "range/v3/algorithm/all_of.hpp"
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace jni {
+namespace org::apache::nifi::minifi::jni {
 
 /**
  * Represents a flow file. Exists to provide the ability to remove
@@ -47,18 +45,17 @@ namespace jni {
  */
 class JniFlowFile : public core::WeakReference {
  public:
-  JniFlowFile(std::shared_ptr<core::FlowFile> ref, const std::shared_ptr<JavaServicer> &servicer, jobject ff)
-      : removed(false),
-        ff_object(ff),
-        ref_(ref),
-        servicer_(servicer) {
+  JniFlowFile(std::shared_ptr<core::FlowFile> ref, std::shared_ptr<JavaServicer> servicer, jobject ff)
+      : ff_object(ff),
+        ref_(std::move(ref)),
+        servicer_(std::move(servicer)) {
   }
 
-  virtual ~JniFlowFile() = default;
+  ~JniFlowFile() override = default;
 
   void remove() override;
 
-  std::shared_ptr<core::FlowFile> get() const {
+  [[nodiscard]] std::shared_ptr<core::FlowFile> get() const {
     return ref_;
   }
 
@@ -71,12 +68,12 @@ class JniFlowFile : public core::WeakReference {
     return ref_ == other.ref_;
   }
 
-  bool empty() const {
+  [[nodiscard]] bool empty() const {
     return removed;
   }
 
  protected:
-  bool removed;
+  bool removed = false;
 
   jobject ff_object;
 
@@ -87,23 +84,13 @@ class JniFlowFile : public core::WeakReference {
   std::shared_ptr<JavaServicer> servicer_;
 };
 
-/**
- * Quick check to determine if a FF is empty.
- */
-struct check_empty_ff : public std::unary_function<std::shared_ptr<JniFlowFile>, bool> {
-  bool operator()(std::shared_ptr<JniFlowFile> session) const {
-    return session->empty();
-  }
-};
-
 /**
  * Jni byte input stream
  */
 class JniByteInputStream {
  public:
   explicit JniByteInputStream(uint64_t size)
-      : buffer_(size),
-        read_size_(0) {
+      : buffer_(size) {
   }
   int64_t operator()(const std::shared_ptr<minifi::io::InputStream>& stream) {
     stream_ = stream;
@@ -147,16 +134,14 @@ class JniByteInputStream {
 
   std::shared_ptr<minifi::io::InputStream> stream_;
   std::vector<std::byte> buffer_;
-  uint64_t read_size_;
 };
 
 class JniInputStream : public core::WeakReference {
  public:
-  JniInputStream(std::unique_ptr<JniByteInputStream> jbi, jobject in_instance, const std::shared_ptr<JavaServicer> &servicer)
-      : removed_(false),
-        in_instance_(in_instance),
+  JniInputStream(std::unique_ptr<JniByteInputStream> jbi, jobject in_instance, std::shared_ptr<JavaServicer> servicer)
+      : in_instance_(in_instance),
         jbi_(std::move(jbi)),
-        servicer_(servicer) {
+        servicer_(std::move(servicer)) {
   }
 
   void remove() override {
@@ -184,7 +169,7 @@ class JniInputStream : public core::WeakReference {
 
  private:
   std::mutex mutex_;
-  bool removed_;
+  bool removed_ = false;
   jobject in_instance_;
   std::unique_ptr<JniByteInputStream> jbi_;
   std::shared_ptr<JavaServicer> servicer_;
@@ -192,21 +177,20 @@ class JniInputStream : public core::WeakReference {
 
 class JniSession : public core::WeakReference {
  public:
-  JniSession(const std::shared_ptr<core::ProcessSession> &session, jobject session_instance, const std::shared_ptr<JavaServicer> &servicer)
-      : removed_(false),
-        session_instance_(session_instance),
-        session_(session),
-        servicer_(servicer) {
+  JniSession(std::shared_ptr<core::ProcessSession> session, jobject session_instance, std::shared_ptr<JavaServicer> servicer)
+      : session_instance_(session_instance),
+        session_(std::move(session)),
+        servicer_(std::move(servicer)) {
   }
 
   void remove() override {
     std::lock_guard<std::mutex> guard(session_mutex_);
     if (!removed_) {
-      for (auto ff : global_ff_objects_) {
+      for (const auto& ff : global_ff_objects_) {
         ff->remove();
       }
 
-      for (auto in : input_streams_) {
+      for (const auto& in : input_streams_) {
         in->remove();
       }
       global_ff_objects_.clear();
@@ -228,13 +212,13 @@ class JniSession : public core::WeakReference {
     return nullptr;
   }
 
-  JniFlowFile * addFlowFile(std::shared_ptr<JniFlowFile> ff) {
+  JniFlowFile * addFlowFile(const std::shared_ptr<JniFlowFile>& ff) {
     std::lock_guard<std::mutex> guard(session_mutex_);
     global_ff_objects_.push_back(ff);
     return ff.get();
   }
 
-  void addInputStream(std::shared_ptr<JniInputStream> in) {
+  void addInputStream(const std::shared_ptr<JniInputStream>& in) {
     std::lock_guard<std::mutex> guard(session_mutex_);
     input_streams_.push_back(in);
   }
@@ -244,7 +228,7 @@ class JniSession : public core::WeakReference {
   }
 
   bool prune() {
-    global_ff_objects_.erase(std::remove_if(global_ff_objects_.begin(), global_ff_objects_.end(), check_empty_ff()), global_ff_objects_.end());
+    ranges::remove_if(global_ff_objects_, [](const std::shared_ptr<JniFlowFile>& flow_file) { return flow_file->empty(); });
     if (global_ff_objects_.empty()) {
       remove();
     }
@@ -252,15 +236,11 @@ class JniSession : public core::WeakReference {
   }
   bool empty() const {
     std::lock_guard<std::mutex> guard(session_mutex_);
-    for (auto ff : global_ff_objects_) {
-      if (!ff->empty())
-        return false;
-    }
-    return true;
+    return ranges::all_of(global_ff_objects_, [](const auto& ff) -> bool { return ff->empty(); });
   }
 
  protected:
-  bool removed_;
+  bool removed_ = false;
   mutable std::mutex session_mutex_;
   jobject session_instance_;
   std::shared_ptr<core::ProcessSession> session_;
@@ -270,25 +250,19 @@ class JniSession : public core::WeakReference {
   std::vector<std::shared_ptr<JniFlowFile>> global_ff_objects_;
 };
 
-struct check_empty : public std::unary_function<std::shared_ptr<JniSession>, bool> {
-  bool operator()(std::shared_ptr<JniSession> session) const {
-    return session->prune();
-  }
-};
-
 class JniSessionFactory : public core::WeakReference {
  public:
-  JniSessionFactory(const std::shared_ptr<core::ProcessSessionFactory> &factory, const std::shared_ptr<JavaServicer> &servicer, jobject java_object)
-      : servicer_(servicer),
-        factory_(factory),
+  JniSessionFactory(std::shared_ptr<core::ProcessSessionFactory> factory, std::shared_ptr<JavaServicer> servicer, jobject java_object)
+      : servicer_(std::move(servicer)),
+        factory_(std::move(factory)),
         java_object_(java_object) {
   }
 
   void remove() override {
     std::lock_guard<std::mutex> guard(session_mutex_);
-    // remove all of the sessions
+    // remove all sessions
     // this should spark their destructor
-    for (auto session : sessions_) {
+    for (const auto& session : sessions_) {
       session->remove();
     }
     sessions_.clear();
@@ -299,24 +273,24 @@ class JniSessionFactory : public core::WeakReference {
     }
   }
 
-  jobject getJavaReference() const {
+  [[nodiscard]] jobject getJavaReference() const {
     return java_object_;
   }
 
-  std::shared_ptr<JavaServicer> getServicer() const {
+  [[nodiscard]] std::shared_ptr<JavaServicer> getServicer() const {
     return servicer_;
   }
 
-  std::shared_ptr<core::ProcessSessionFactory> getFactory() const {
+  [[nodiscard]] std::shared_ptr<core::ProcessSessionFactory> getFactory() const {
     return factory_;
   }
 
   /**
    */
-  JniSession *addSession(std::shared_ptr<JniSession> session) {
+  JniSession *addSession(const std::shared_ptr<JniSession>& session) {
     std::lock_guard<std::mutex> guard(session_mutex_);
 
-    sessions_.erase(std::remove_if(sessions_.begin(), sessions_.end(), check_empty()), sessions_.end());
+    ranges::remove_if(sessions_, [](const auto& session) { return session->prune(); });
 
     sessions_.push_back(session);
 
@@ -336,8 +310,4 @@ class JniSessionFactory : public core::WeakReference {
 
 
 
-} /* namespace jni */
-} /* namespace minifi */
-} /* namespace nifi */
-} /* namespace apache */
-} /* namespace org */
+}  // namespace org::apache::nifi::minifi::jni
diff --git a/extensions/libarchive/ArchiveMetadata.h b/extensions/libarchive/ArchiveMetadata.h
index 712c1287d..9ce063040 100644
--- a/extensions/libarchive/ArchiveMetadata.h
+++ b/extensions/libarchive/ArchiveMetadata.h
@@ -41,7 +41,7 @@ class ArchiveEntryMetadata {
   uint64_t entryMTimeNsec;
   uint64_t entrySize;
 
-  std::string tmpFileName;
+  std::filesystem::path tmpFileName;
   std::string stashKey;
 
   inline rapidjson::Value toJson(rapidjson::Document::AllocatorType &alloc) const;
diff --git a/extensions/libarchive/ArchiveTests.h b/extensions/libarchive/ArchiveTests.h
index 36fdb1955..7f349b3b4 100644
--- a/extensions/libarchive/ArchiveTests.h
+++ b/extensions/libarchive/ArchiveTests.h
@@ -20,6 +20,7 @@
 
 #pragma once
 
+#include <filesystem>
 #include <map>
 #include <vector>
 #include <string>
@@ -55,9 +56,9 @@ FN_VEC_T build_test_archive_order(int, const char**);
 
 OrderedTestArchive build_ordered_test_archive(int, const char**, const char**);
 
-void build_test_archive(const std::string&, const TAE_MAP_T& entries, FN_VEC_T order = FN_VEC_T());
-void build_test_archive(const std::string&, OrderedTestArchive&);
+void build_test_archive(const std::filesystem::path&, const TAE_MAP_T& entries, FN_VEC_T order = FN_VEC_T());
+void build_test_archive(const std::filesystem::path&, OrderedTestArchive&);
 
-bool check_archive_contents(const std::string&, const TAE_MAP_T& entries, bool check_attributes = true, const FN_VEC_T& order = FN_VEC_T());
-bool check_archive_contents(const std::string&, const OrderedTestArchive&, bool check_attributes = true);
+bool check_archive_contents(const std::filesystem::path&, const TAE_MAP_T& entries, bool check_attributes = true, const FN_VEC_T& order = FN_VEC_T());
+bool check_archive_contents(const std::filesystem::path&, const OrderedTestArchive&, bool check_attributes = true);
 
diff --git a/extensions/libarchive/FocusArchiveEntry.cpp b/extensions/libarchive/FocusArchiveEntry.cpp
index cd057f2d9..5c19a30f7 100644
--- a/extensions/libarchive/FocusArchiveEntry.cpp
+++ b/extensions/libarchive/FocusArchiveEntry.cpp
@@ -69,8 +69,8 @@ void FocusArchiveEntry::onTrigger(core::ProcessContext *context, core::ProcessSe
 
   for (auto &entryMetadata : archiveMetadata.entryMetadata) {
     if (entryMetadata.entryType == AE_IFREG) {
-      logger_->log_info("FocusArchiveEntry importing %s from %s", entryMetadata.entryName, entryMetadata.tmpFileName);
-      session->import(entryMetadata.tmpFileName, flowFile, false, 0);
+      logger_->log_info("FocusArchiveEntry importing %s from %s", entryMetadata.entryName, entryMetadata.tmpFileName.string());
+      session->import(entryMetadata.tmpFileName.string(), flowFile, false, 0);
       utils::Identifier stashKeyUuid = id_generator_->generate();
       logger_->log_debug("FocusArchiveEntry generated stash key %s for entry %s", stashKeyUuid.to_string(), entryMetadata.entryName);
       entryMetadata.stashKey = stashKeyUuid.to_string();
@@ -212,9 +212,9 @@ int64_t FocusArchiveEntry::ReadCallback::operator()(const std::shared_ptr<io::In
       auto tmpFileName = file_man_->unique_file(true);
       metadata.tmpFileName = tmpFileName;
       metadata.entryType = entryType;
-      logger_->log_info("FocusArchiveEntry extracting %s to: %s", entryName, tmpFileName);
+      logger_->log_info("FocusArchiveEntry extracting %s to: %s", entryName, tmpFileName.string());
 
-      auto fd = fopen(tmpFileName.c_str(), "w");
+      auto fd = fopen(tmpFileName.string().c_str(), "w");
 
       if (archive_entry_size(entry) > 0) {
 #ifdef WIN32
diff --git a/extensions/libarchive/ManipulateArchive.cpp b/extensions/libarchive/ManipulateArchive.cpp
index 50e05d7e3..7a96cedcb 100644
--- a/extensions/libarchive/ManipulateArchive.cpp
+++ b/extensions/libarchive/ManipulateArchive.cpp
@@ -36,11 +36,7 @@
 #include "FocusArchiveEntry.h"
 #include "UnfocusArchiveEntry.h"
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace processors {
+namespace org::apache::nifi::minifi::processors {
 
 const core::Property ManipulateArchive::Operation("Operation", "Operation to perform on the archive (touch, remove, copy, move).", "");
 const core::Property ManipulateArchive::Target("Target", "An existing entry within the archive to perform the operation on.", "");
@@ -95,7 +91,7 @@ void ManipulateArchive::onSchedule(core::ProcessContext *context, core::ProcessS
     }
 
     // Users may specify one or none of before or after, but never both.
-    if (before_.size() && after_.size()) {
+    if (!before_.empty() && !after_.empty()) {
         logger_->log_error("ManipulateArchive: cannot specify both before and after.");
         invalid = true;
     }
@@ -143,7 +139,7 @@ void ManipulateArchive::onTrigger(core::ProcessContext* /*context*/, core::Proce
 
     auto position = entries_end;
 
-    // Small speedup for when neither before or after are provided or needed
+    // Small speedup for when neither before nor after are provided or needed
     if ((!before_.empty() || !after_.empty()) && operation_ != OPERATION_REMOVE) {
         std::string positionEntry = after_.empty() ? before_ : after_;
         position = archiveMetadata.find(positionEntry);
@@ -164,7 +160,7 @@ void ManipulateArchive::onTrigger(core::ProcessContext* /*context*/, core::Proce
     }
 
     if (operation_ == OPERATION_REMOVE) {
-        std::remove((*target_position).tmpFileName.c_str());
+        std::filesystem::remove((*target_position).tmpFileName);
         target_position = archiveMetadata.eraseEntry(target_position);
     } else if (operation_ == OPERATION_COPY) {
         ArchiveEntryMetadata copy = *target_position;
@@ -204,8 +200,4 @@ void ManipulateArchive::onTrigger(core::ProcessContext* /*context*/, core::Proce
 
 REGISTER_RESOURCE(ManipulateArchive, Processor);
 
-} /* namespace processors */
-} /* namespace minifi */
-} /* namespace nifi */
-} /* namespace apache */
-} /* namespace org */
+}  // namespace org::apache::nifi::minifi::processors
diff --git a/extensions/libarchive/UnfocusArchiveEntry.cpp b/extensions/libarchive/UnfocusArchiveEntry.cpp
index fe1f86f36..9bc1be0a5 100644
--- a/extensions/libarchive/UnfocusArchiveEntry.cpp
+++ b/extensions/libarchive/UnfocusArchiveEntry.cpp
@@ -92,8 +92,8 @@ void UnfocusArchiveEntry::onTrigger(core::ProcessContext *context, core::Process
     }
 
     if (entry.entryName == lensArchiveMetadata.focusedEntry) {
-      logger_->log_debug("UnfocusArchiveEntry exporting focused entry to %s", entry.tmpFileName);
-      session->exportContent(entry.tmpFileName, flowFile, false);
+      logger_->log_debug("UnfocusArchiveEntry exporting focused entry to %s", entry.tmpFileName.string());
+      session->exportContent(entry.tmpFileName.string(), flowFile, false);
     }
   }
 
@@ -107,10 +107,10 @@ void UnfocusArchiveEntry::onTrigger(core::ProcessContext *context, core::Process
       continue;
     }
 
-    logger_->log_debug("UnfocusArchiveEntry exporting entry %s to %s", entry.stashKey, entry.tmpFileName);
+    logger_->log_debug("UnfocusArchiveEntry exporting entry %s to %s", entry.stashKey, entry.tmpFileName.string());
     session->restore(entry.stashKey, flowFile);
     // TODO(calebj) implement copy export/don't worry about multiple claims/optimal efficiency for *now*
-    session->exportContent(entry.tmpFileName, flowFile, false);
+    session->exportContent(entry.tmpFileName.string(), flowFile, false);
   }
 
   if (lensArchiveMetadata.archiveName.empty()) {
@@ -170,9 +170,9 @@ int64_t UnfocusArchiveEntry::WriteCallback::operator()(const std::shared_ptr<io:
     logger_->log_info("UnfocusArchiveEntry writing entry %s", entryMetadata.entryName);
 
     if (entryMetadata.entryType == AE_IFREG && entryMetadata.entrySize > 0) {
-      size_t stat_ok = stat(entryMetadata.tmpFileName.c_str(), &st);
+      size_t stat_ok = stat(entryMetadata.tmpFileName.string().c_str(), &st);
       if (stat_ok != 0) {
-        logger_->log_error("Error statting %s: %s", entryMetadata.tmpFileName, std::system_category().default_error_condition(errno).message());
+        logger_->log_error("Error statting %s: %s", entryMetadata.tmpFileName.string(), std::system_category().default_error_condition(errno).message());
       }
       archive_entry_copy_stat(entry, &st);
     }
@@ -194,7 +194,7 @@ int64_t UnfocusArchiveEntry::WriteCallback::operator()(const std::shared_ptr<io:
     if (entryMetadata.entryType == AE_IFREG && entryMetadata.entrySize > 0) {
       logger_->log_info("UnfocusArchiveEntry writing %d bytes of "
                         "data from tmp file %s to archive entry %s",
-                        st.st_size, entryMetadata.tmpFileName, entryMetadata.entryName);
+                        st.st_size, entryMetadata.tmpFileName.string(), entryMetadata.entryName);
       std::ifstream ifs(entryMetadata.tmpFileName, std::ifstream::in | std::ios::binary);
 
       while (ifs.good()) {
@@ -213,7 +213,7 @@ int64_t UnfocusArchiveEntry::WriteCallback::operator()(const std::shared_ptr<io:
       ifs.close();
 
       // Remove the tmp file as we are through with it
-      std::remove(entryMetadata.tmpFileName.c_str());
+      std::filesystem::remove(entryMetadata.tmpFileName);
     }
 
     archive_entry_clear(entry);
diff --git a/extensions/librdkafka/KafkaProcessorBase.cpp b/extensions/librdkafka/KafkaProcessorBase.cpp
index 7e57b5ce4..d71d52fb9 100644
--- a/extensions/librdkafka/KafkaProcessorBase.cpp
+++ b/extensions/librdkafka/KafkaProcessorBase.cpp
@@ -36,12 +36,12 @@ void KafkaProcessorBase::setKafkaAuthenticationParameters(core::ProcessContext&
       if (ssl_data->ca_loc.empty() && ssl_data->cert_loc.empty() && ssl_data->key_loc.empty() && ssl_data->key_pw.empty()) {
         logger_->log_warn("Security protocol is set to %s, but no valid security parameters are set in the properties or in the SSL Context Service.", security_protocol_.toString());
       } else {
-        utils::setKafkaConfigurationField(*config, "ssl.ca.location", ssl_data->ca_loc);
-        logger_->log_debug("Kafka ssl.ca.location [%s]", ssl_data->ca_loc);
-        utils::setKafkaConfigurationField(*config, "ssl.certificate.location", ssl_data->cert_loc);
-        logger_->log_debug("Kafka ssl.certificate.location [%s]", ssl_data->cert_loc);
-        utils::setKafkaConfigurationField(*config, "ssl.key.location", ssl_data->key_loc);
-        logger_->log_debug("Kafka ssl.key.location [%s]", ssl_data->key_loc);
+        utils::setKafkaConfigurationField(*config, "ssl.ca.location", ssl_data->ca_loc.string());
+        logger_->log_debug("Kafka ssl.ca.location [%s]", ssl_data->ca_loc.string());
+        utils::setKafkaConfigurationField(*config, "ssl.certificate.location", ssl_data->cert_loc.string());
+        logger_->log_debug("Kafka ssl.certificate.location [%s]", ssl_data->cert_loc.string());
+        utils::setKafkaConfigurationField(*config, "ssl.key.location", ssl_data->key_loc.string());
+        logger_->log_debug("Kafka ssl.key.location [%s]", ssl_data->key_loc.string());
         utils::setKafkaConfigurationField(*config, "ssl.key.password", ssl_data->key_pw);
         logger_->log_debug("Kafka ssl.key.password was set");
       }
diff --git a/extensions/librdkafka/PublishKafka.cpp b/extensions/librdkafka/PublishKafka.cpp
index 6c141fc24..7bfd534f1 100644
--- a/extensions/librdkafka/PublishKafka.cpp
+++ b/extensions/librdkafka/PublishKafka.cpp
@@ -638,10 +638,14 @@ std::optional<utils::net::SslData> PublishKafka::getSslData(core::ProcessContext
   }
 
   utils::net::SslData ssl_data;
-  context.getProperty(SecurityCA.getName(), ssl_data.ca_loc);
-  context.getProperty(SecurityCert.getName(), ssl_data.cert_loc);
-  context.getProperty(SecurityPrivateKey.getName(), ssl_data.key_loc);
-  context.getProperty(SecurityPrivateKeyPassWord.getName(), ssl_data.key_pw);
+  if (auto security_ca = context.getProperty(SecurityCA))
+    ssl_data.ca_loc = *security_ca;
+  if (auto security_cert = context.getProperty(SecurityCert))
+    ssl_data.cert_loc = *security_cert;
+  if (auto security_private_key = context.getProperty(SecurityPrivateKey))
+    ssl_data.key_loc = *security_private_key;
+  if (auto security_private_key_pass = context.getProperty(SecurityPrivateKeyPassWord))
+    ssl_data.key_pw = *security_private_key_pass;
   return ssl_data;
 }
 
diff --git a/extensions/pcap/CapturePacket.cpp b/extensions/pcap/CapturePacket.cpp
index 5b71c0791..e25399bb6 100644
--- a/extensions/pcap/CapturePacket.cpp
+++ b/extensions/pcap/CapturePacket.cpp
@@ -61,12 +61,12 @@ const core::Property CapturePacket::CaptureBluetooth(core::PropertyBuilder::crea
 
 const core::Relationship CapturePacket::Success("success", "All files are routed to success");
 
-std::string CapturePacket::generate_new_pcap(const std::string &base_path) {
-  std::string path = base_path;
+std::filesystem::path CapturePacket::generate_new_pcap(const std::string &base_path) {
+  auto path = base_path;
   // can use relaxed for a counter
   int cnt = num_.fetch_add(1, std::memory_order_relaxed);
   std::string filename = std::to_string(cnt);
-  path.append(filename);
+  path += filename;
   return path;
 }
 
@@ -101,7 +101,7 @@ CapturePacketMechanism *CapturePacket::create_new_capture(const std::string &bas
   auto new_capture = new CapturePacketMechanism(base_path, generate_new_pcap(base_path), max_size);
   new_capture->writer_ = new pcpp::PcapFileWriterDevice(new_capture->getFile());
   if (!new_capture->writer_->open())
-    throw std::runtime_error{utils::StringUtils::join_pack("Failed to open PcapFileWriterDevice with file ", new_capture->getFile())};
+    throw std::runtime_error{utils::StringUtils::join_pack("Failed to open PcapFileWriterDevice with file ", new_capture->getFile().string())};
 
   return new_capture;
 }
@@ -143,7 +143,7 @@ void CapturePacket::onSchedule(const std::shared_ptr<core::ProcessContext> &cont
 
   utils::Identifier dir_ext = id_generator_->generate();
 
-  base_path_ = dir_ext.to_string();
+  base_path_ = std::filesystem::path(dir_ext.to_string());
 
   const std::vector<pcpp::PcapLiveDevice*>& devList = pcpp::PcapLiveDeviceList::getInstance().getPcapLiveDevicesList();
   for (auto iter : devList) {
diff --git a/extensions/pcap/CapturePacket.h b/extensions/pcap/CapturePacket.h
index 893d33867..5f692b155 100644
--- a/extensions/pcap/CapturePacket.h
+++ b/extensions/pcap/CapturePacket.h
@@ -62,11 +62,11 @@ class CapturePacketMechanism {
 
   pcpp::PcapFileWriterDevice *writer_;
 
-  const std::string &getBasePath() {
+  const std::filesystem::path &getBasePath() {
     return path_;
   }
 
-  const std::string &getFile() {
+  const std::filesystem::path &getFile() {
     return file_;
   }
 
@@ -76,8 +76,8 @@ class CapturePacketMechanism {
 
  protected:
   CapturePacketMechanism &operator=(const CapturePacketMechanism &other) = delete;
-  std::string path_;
-  std::string file_;
+  std::filesystem::path path_;
+  std::filesystem::path file_;
   int64_t *max_size_;
   std::atomic<int64_t> atomic_count_;
 };
@@ -138,30 +138,30 @@ class CapturePacket : public core::Processor {
     logger_->log_trace("Stopped device capture. clearing queues");
     CapturePacketMechanism *capture;
     while (mover->source.try_dequeue(capture)) {
-      std::remove(capture->getFile().c_str());
+      std::filesystem::remove(capture->getFile());
       delete capture;
     }
     logger_->log_trace("Cleared source queue");
     while (mover->sink.try_dequeue(capture)) {
-      std::remove(capture->getFile().c_str());
+      std::filesystem::remove(capture->getFile());
       delete capture;
     }
     device_list_.clear();
     logger_->log_trace("Cleared sink queue");
   }
 
-  static std::string generate_new_pcap(const std::string &base_path);
+  static std::filesystem::path generate_new_pcap(const std::string &base_path);
 
   static CapturePacketMechanism *create_new_capture(const std::string &base_path, int64_t *max_size);
 
  private:
-  inline std::string getPath() {
-    return base_dir_ + "/" + base_path_;
+  inline std::filesystem::path getPath() {
+    return base_dir_ / base_path_;
   }
   bool capture_bluetooth_ = false;
-  std::string base_dir_;
+  std::filesystem::path base_dir_;
   std::vector<std::string> attached_controllers_;
-  std::string base_path_;
+  std::filesystem::path base_path_;
   int64_t pcap_batch_size_ = 50;
   std::unique_ptr<PacketMovers> mover;
   static std::atomic<int> num_;
diff --git a/extensions/pdh/tests/PerformanceDataMonitorTests.cpp b/extensions/pdh/tests/PerformanceDataMonitorTests.cpp
index 8dbda2547..ca260a600 100644
--- a/extensions/pdh/tests/PerformanceDataMonitorTests.cpp
+++ b/extensions/pdh/tests/PerformanceDataMonitorTests.cpp
@@ -42,7 +42,7 @@ class PerformanceDataMonitorTester {
     plan_ = test_controller_.createPlan();
     performance_monitor_ = plan_->addProcessor("PerformanceDataMonitor", "pdhsys");
     putfile_ = plan_->addProcessor("PutFile", "putfile", core::Relationship("success", "description"), true);
-    plan_->setProperty(putfile_, PutFile::Directory.getName(), dir_);
+    plan_->setProperty(putfile_, PutFile::Directory.getName(), dir_.string());
   }
 
   bool runWithRetries(std::function<bool()>&& assertions, uint32_t max_tries = 10) {
@@ -63,7 +63,7 @@ class PerformanceDataMonitorTester {
   }
 
   TestController test_controller_;
-  std::string dir_;
+  std::filesystem::path dir_;
   std::shared_ptr<TestPlan> plan_;
   std::shared_ptr<core::Processor> performance_monitor_;
   std::shared_ptr<core::Processor> putfile_;
@@ -89,8 +89,8 @@ TEST_CASE("PerformanceDataMonitorPartiallyInvalidGroupPropertyTest", "[performan
 
     bool ff_contains_all_data = false;
 
-    const auto lambda = [&ff_contains_all_data](const std::string& path, const std::string& filename) -> bool {
-      FILE* fp = fopen((path + utils::file::FileUtils::get_separator() + filename).c_str(), "r");
+    const auto lambda = [&ff_contains_all_data](const std::filesystem::path& path, const std::filesystem::path& filename) -> bool {
+      FILE* fp = fopen((path / filename).string().c_str(), "r");
       REQUIRE(fp != nullptr);
       char readBuffer[500];
       rapidjson::FileReadStream is(fp, readBuffer, sizeof(readBuffer));
@@ -118,8 +118,8 @@ TEST_CASE("PerformanceDataMonitorCustomPDHCountersTest", "[performancedatamonito
 
   const auto assertions = [&tester]() {
     bool ff_contains_all_data = false;
-    const auto lambda = [&ff_contains_all_data](const std::string& path, const std::string& filename) -> bool {
-      FILE* fp = fopen((path + utils::file::FileUtils::get_separator() + filename).c_str(), "r");
+    const auto lambda = [&ff_contains_all_data](const std::filesystem::path& path, const std::filesystem::path& filename) -> bool {
+      FILE* fp = fopen((path / filename).string().c_str(), "r");
       REQUIRE(fp != nullptr);
       char readBuffer[50000];
       rapidjson::FileReadStream is(fp, readBuffer, sizeof(readBuffer));
@@ -152,8 +152,8 @@ TEST_CASE("PerformanceDataMonitorCustomPDHCountersTestOpenTelemetry", "[performa
 
   const auto assertions = [&tester]() {
     bool ff_contains_all_data = false;
-    const auto lambda = [&ff_contains_all_data](const std::string& path, const std::string& filename) -> bool {
-      FILE* fp = fopen((path + utils::file::FileUtils::get_separator() + filename).c_str(), "r");
+    const auto lambda = [&ff_contains_all_data](const std::filesystem::path& path, const std::filesystem::path& filename) -> bool {
+      FILE* fp = fopen((path / filename).string().c_str(), "r");
       REQUIRE(fp != nullptr);
       char readBuffer[50000];
       rapidjson::FileReadStream is(fp, readBuffer, sizeof(readBuffer));
@@ -188,8 +188,8 @@ TEST_CASE("PerformanceDataMonitorAllPredefinedGroups", "[performancedatamonitora
 
   const auto assertions = [&tester]() {
     bool ff_contains_all_data = false;
-    const auto lambda = [&ff_contains_all_data](const std::string& path, const std::string& filename) -> bool {
-      FILE* fp = fopen((path + utils::file::FileUtils::get_separator() + filename).c_str(), "r");
+    const auto lambda = [&ff_contains_all_data](const std::filesystem::path& path, const std::filesystem::path& filename) -> bool {
+      FILE* fp = fopen((path / filename).string().c_str(), "r");
       REQUIRE(fp != nullptr);
       char readBuffer[50000];
       rapidjson::FileReadStream is(fp, readBuffer, sizeof(readBuffer));
diff --git a/extensions/rocksdb-repos/DatabaseContentRepository.cpp b/extensions/rocksdb-repos/DatabaseContentRepository.cpp
index ccd71c995..c038582c3 100644
--- a/extensions/rocksdb-repos/DatabaseContentRepository.cpp
+++ b/extensions/rocksdb-repos/DatabaseContentRepository.cpp
@@ -36,7 +36,7 @@ bool DatabaseContentRepository::initialize(const std::shared_ptr<minifi::Configu
   if (configuration->get(Configure::nifi_dbcontent_repository_directory_default, value)) {
     directory_ = value;
   } else {
-    directory_ = configuration->getHome() + "/dbcontentrepository";
+    directory_ = (configuration->getHome() / "dbcontentrepository").string();
   }
   const auto encrypted_env = createEncryptingEnv(utils::crypto::EncryptionManager{configuration->getHome()}, DbEncryptionOptions{directory_, ENCRYPTION_KEY_NAME});
   logger_->log_info("Using %s DatabaseContentRepository", encrypted_env ? "encrypted" : "plaintext");
diff --git a/extensions/rocksdb-repos/FlowFileRepository.cpp b/extensions/rocksdb-repos/FlowFileRepository.cpp
index 2412bf97f..23695536f 100644
--- a/extensions/rocksdb-repos/FlowFileRepository.cpp
+++ b/extensions/rocksdb-repos/FlowFileRepository.cpp
@@ -127,7 +127,7 @@ void FlowFileRepository::run() {
 }
 
 void FlowFileRepository::prune_stored_flowfiles() {
-  const auto encrypted_env = createEncryptingEnv(utils::crypto::EncryptionManager{config_->getHome()}, DbEncryptionOptions{checkpoint_dir_, ENCRYPTION_KEY_NAME});
+  const auto encrypted_env = createEncryptingEnv(utils::crypto::EncryptionManager{config_->getHome()}, DbEncryptionOptions{checkpoint_dir_.string(), ENCRYPTION_KEY_NAME});
   logger_->log_info("Using %s FlowFileRepository checkpoint", encrypted_env ? "encrypted" : "plaintext");
 
   auto set_db_opts = [encrypted_env] (minifi::internal::Writable<rocksdb::DBOptions>& db_opts) {
@@ -140,14 +140,14 @@ void FlowFileRepository::prune_stored_flowfiles() {
       db_opts.set(&rocksdb::DBOptions::env, rocksdb::Env::Default());
     }
   };
-  auto checkpointDB = minifi::internal::RocksDatabase::create(set_db_opts, {}, checkpoint_dir_, minifi::internal::RocksDbMode::ReadOnly);
+  auto checkpointDB = minifi::internal::RocksDatabase::create(set_db_opts, {}, checkpoint_dir_.string(), minifi::internal::RocksDbMode::ReadOnly);
   std::optional<minifi::internal::OpenRocksDb> opendb;
   if (nullptr != checkpoint_) {
     opendb = checkpointDB->open();
     if (opendb) {
-      logger_->log_trace("Successfully opened checkpoint database at '%s'", checkpoint_dir_);
+      logger_->log_trace("Successfully opened checkpoint database at '%s'", checkpoint_dir_.string());
     } else {
-      logger_->log_error("Couldn't open checkpoint database at '%s' using live database", checkpoint_dir_);
+      logger_->log_error("Couldn't open checkpoint database at '%s' using live database", checkpoint_dir_.string());
       opendb = db_->open();
     }
     if (!opendb) {
@@ -230,7 +230,7 @@ void FlowFileRepository::initialize_repository() {
   }
   // delete any previous copy
   if (utils::file::delete_dir(checkpoint_dir_) < 0) {
-    logger_->log_error("Could not delete existing checkpoint directory '%s'", checkpoint_dir_);
+    logger_->log_error("Could not delete existing checkpoint directory '%s'", checkpoint_dir_.string());
     return;
   }
   std::unique_ptr<rocksdb::Checkpoint> checkpoint;
@@ -239,13 +239,13 @@ void FlowFileRepository::initialize_repository() {
     logger_->log_error("Could not create checkpoint object: %s", checkpoint_status.ToString());
     return;
   }
-  checkpoint_status = checkpoint->CreateCheckpoint(checkpoint_dir_);
+  checkpoint_status = checkpoint->CreateCheckpoint(checkpoint_dir_.string());
   if (!checkpoint_status.ok()) {
     logger_->log_error("Could not initialize checkpoint: %s", checkpoint_status.ToString());
     return;
   }
   checkpoint_ = std::move(checkpoint);
-  logger_->log_trace("Created checkpoint in directory '%s'", checkpoint_dir_);
+  logger_->log_trace("Created checkpoint in directory '%s'", checkpoint_dir_.string());
 }
 
 void FlowFileRepository::loadComponent(const std::shared_ptr<core::ContentRepository> &content_repo) {
diff --git a/extensions/rocksdb-repos/FlowFileRepository.h b/extensions/rocksdb-repos/FlowFileRepository.h
index 1b5377192..2edb58427 100644
--- a/extensions/rocksdb-repos/FlowFileRepository.h
+++ b/extensions/rocksdb-repos/FlowFileRepository.h
@@ -65,8 +65,8 @@ class FlowFileRepository : public ThreadedRepository, public SwapManager {
       : FlowFileRepository(std::move(name)) {
   }
 
-  explicit FlowFileRepository(std::string repo_name = "",
-                     std::string checkpoint_dir = FLOWFILE_CHECKPOINT_DIRECTORY,
+  explicit FlowFileRepository(const std::string& repo_name = "",
+                     std::filesystem::path checkpoint_dir = FLOWFILE_CHECKPOINT_DIRECTORY,
                      std::string directory = FLOWFILE_REPOSITORY_DIRECTORY,
                      std::chrono::milliseconds maxPartitionMillis = MAX_FLOWFILE_REPOSITORY_ENTRY_LIFE_TIME,
                      int64_t maxPartitionBytes = MAX_FLOWFILE_REPOSITORY_STORAGE_SIZE,
@@ -236,7 +236,7 @@ class FlowFileRepository : public ThreadedRepository, public SwapManager {
     return thread_;
   }
 
-  std::string checkpoint_dir_;
+  std::filesystem::path checkpoint_dir_;
   moodycamel::ConcurrentQueue<std::string> keys_to_delete;
   std::shared_ptr<core::ContentRepository> content_repo_;
   std::unique_ptr<minifi::internal::RocksDatabase> db_;
diff --git a/extensions/script/ExecuteScript.cpp b/extensions/script/ExecuteScript.cpp
index 27688b383..28717c122 100644
--- a/extensions/script/ExecuteScript.cpp
+++ b/extensions/script/ExecuteScript.cpp
@@ -26,11 +26,14 @@
 #include <PythonScriptEngine.h>
 #endif  // PYTHON_SUPPORT
 
+#include <vector>
+
 #include "ExecuteScript.h"
 #include "core/PropertyBuilder.h"
 #include "core/Resource.h"
 #include "utils/ProcessorConfigUtils.h"
 #include "utils/StringUtils.h"
+#include "range/v3/range/conversion.hpp"
 
 namespace org::apache::nifi::minifi::processors {
 
@@ -127,7 +130,7 @@ void ExecuteScript::onTrigger(const std::shared_ptr<core::ProcessContext> &conte
   }
 
   if (module_directory_) {
-    engine->setModulePaths(utils::StringUtils::splitAndTrimRemovingEmpty(*module_directory_, ","));
+    engine->setModulePaths(utils::StringUtils::splitAndTrimRemovingEmpty(*module_directory_, ",") | ranges::to<std::vector<std::filesystem::path>>());
   }
 
   if (!script_body_.empty()) {
diff --git a/extensions/script/ScriptEngine.h b/extensions/script/ScriptEngine.h
index 8a02ce25f..125f76566 100644
--- a/extensions/script/ScriptEngine.h
+++ b/extensions/script/ScriptEngine.h
@@ -21,6 +21,7 @@
 #include <string>
 #include <vector>
 #include <utility>
+#include <filesystem>
 
 namespace org::apache::nifi::minifi::script {
 
@@ -40,7 +41,7 @@ class ScriptEngine {
    */
   virtual void evalFile(const std::string &file_name) = 0;
 
-  void setModulePaths(std::vector<std::string>&& module_paths) {
+  void setModulePaths(std::vector<std::filesystem::path>&& module_paths) {
     module_paths_ = std::move(module_paths);
   }
 
@@ -49,7 +50,7 @@ class ScriptEngine {
   virtual ~ScriptEngine() = default;
 
  protected:
-  std::vector<std::string> module_paths_;
+  std::vector<std::filesystem::path> module_paths_;
 };
 
 }  // namespace org::apache::nifi::minifi::script
diff --git a/extensions/script/lua/LuaScriptEngine.cpp b/extensions/script/lua/LuaScriptEngine.cpp
index 309a89d80..bce738dfd 100644
--- a/extensions/script/lua/LuaScriptEngine.cpp
+++ b/extensions/script/lua/LuaScriptEngine.cpp
@@ -65,9 +65,9 @@ LuaScriptEngine::LuaScriptEngine() {
 void LuaScriptEngine::executeScriptWithAppendedModulePaths(std::string& script) {
   for (const auto& module_path : module_paths_) {
     if (std::filesystem::is_regular_file(std::filesystem::status(module_path))) {
-      script = utils::StringUtils::join_pack("package.path = package.path .. [[;", module_path, "]]\n", script);
+      script = utils::StringUtils::join_pack("package.path = package.path .. [[;", module_path.string(), "]]\n", script);
     } else {
-      script = utils::StringUtils::join_pack("package.path = package.path .. [[;", module_path, "/?.lua]]\n", script);
+      script = utils::StringUtils::join_pack("package.path = package.path .. [[;", module_path.string(), "/?.lua]]\n", script);
     }
   }
   lua_.script(script, sol::script_throw_on_error);
diff --git a/extensions/script/lua/LuaScriptEngine.h b/extensions/script/lua/LuaScriptEngine.h
index 1e7d7521d..a1aa366ac 100644
--- a/extensions/script/lua/LuaScriptEngine.h
+++ b/extensions/script/lua/LuaScriptEngine.h
@@ -47,7 +47,7 @@ class LuaScriptEngine : public script::ScriptEngine {
    */
   template<typename... Args>
   void call(const std::string &fn_name, Args &&...args) {
-    sol::protected_function_result function_result;
+    sol::protected_function_result function_result{};
     try {
       sol::protected_function fn = lua_[fn_name.c_str()];
       function_result = fn(convert(args)...);
@@ -97,11 +97,11 @@ class LuaScriptEngine : public script::ScriptEngine {
     return value;
   }
 
-  std::shared_ptr<script::ScriptProcessContext> convert(const std::shared_ptr<core::ProcessContext> &context) {
+  static std::shared_ptr<script::ScriptProcessContext> convert(const std::shared_ptr<core::ProcessContext> &context) {
     return std::make_shared<script::ScriptProcessContext>(context);
   }
 
-  std::shared_ptr<LuaProcessSession> convert(const std::shared_ptr<core::ProcessSession> &session) {
+  static std::shared_ptr<LuaProcessSession> convert(const std::shared_ptr<core::ProcessSession> &session) {
     return std::make_shared<LuaProcessSession>(session);
   }
 
diff --git a/extensions/script/python/ExecutePythonProcessor.cpp b/extensions/script/python/ExecutePythonProcessor.cpp
index 5f6b6ddaa..e173e3e1f 100644
--- a/extensions/script/python/ExecutePythonProcessor.cpp
+++ b/extensions/script/python/ExecutePythonProcessor.cpp
@@ -29,6 +29,7 @@
 #include "utils/file/FileUtils.h"
 #include "core/PropertyBuilder.h"
 #include "core/Resource.h"
+#include "range/v3/range/conversion.hpp"
 
 namespace org::apache::nifi::minifi::python::processors {
 
@@ -118,7 +119,7 @@ void ExecutePythonProcessor::appendPathForImportModules() {
   std::string module_directory;
   getProperty(ModuleDirectory.getName(), module_directory);
   if (!module_directory.empty()) {
-    python_script_engine_->setModulePaths(utils::StringUtils::splitAndTrimRemovingEmpty(module_directory, ","));
+    python_script_engine_->setModulePaths(utils::StringUtils::splitAndTrimRemovingEmpty(module_directory, ",") | ranges::to<std::vector<std::filesystem::path>>());
   }
 }
 
diff --git a/extensions/script/python/PythonCreator.h b/extensions/script/python/PythonCreator.h
index b8a36a870..5a61416e1 100644
--- a/extensions/script/python/PythonCreator.h
+++ b/extensions/script/python/PythonCreator.h
@@ -63,26 +63,26 @@ class PythonCreator : public minifi::core::CoreComponent {
     configure({pathListings.value()});
 
     for (const auto &path : classpaths_) {
-      const auto script_name = getScriptName(path);
-      const auto package = getPackage(pathListings.value(), path);
-      std::string class_name = script_name;
-      std::string full_name = "org.apache.nifi.minifi.processors." + script_name;
+      const auto script_name = path.stem();
+      const auto package = getPackage(pathListings.value(), path.string());
+      std::string class_name = script_name.string();
+      std::string full_name = "org.apache.nifi.minifi.processors." + script_name.string();
       if (!package.empty()) {
-        full_name = utils::StringUtils::join_pack("org.apache.nifi.minifi.processors.", package, ".", script_name);
+        full_name = utils::StringUtils::join_pack("org.apache.nifi.minifi.processors.", package, ".", script_name.string());
         class_name = full_name;
       }
-      core::getClassLoader().registerClass(class_name, std::make_unique<PythonObjectFactory>(path, class_name));
+      core::getClassLoader().registerClass(class_name, std::make_unique<PythonObjectFactory>(path.string(), class_name));
       registered_classes_.push_back(class_name);
       try {
-        registerScriptDescription(class_name, full_name, path, script_name);
+        registerScriptDescription(class_name, full_name, path, script_name.string());
       } catch (const std::exception &err) {
-        logger_->log_error("Cannot load %s: %s", script_name, err.what());
+        logger_->log_error("Cannot load %s: %s", script_name.string(), err.what());
       }
     }
   }
 
  private:
-  void registerScriptDescription(const std::string& class_name, const std::string& full_name, const std::string& path, const std::string& script_name) {
+  void registerScriptDescription(const std::string& class_name, const std::string& full_name, const std::filesystem::path& path, const std::string& script_name) {
     auto processor = core::ClassLoader::getDefaultClassLoader().instantiate<python::processors::ExecutePythonProcessor>(class_name, utils::IdGenerator::getIdGenerator()->generate());
     if (!processor) {
       logger_->log_error("Couldn't instantiate '%s' python processor", class_name);
@@ -90,7 +90,7 @@ class PythonCreator : public minifi::core::CoreComponent {
     }
     processor->initialize();
     minifi::BundleDetails details;
-    details.artifact = getFileName(path);
+    details.artifact = path.filename().string();
     details.version = minifi::AgentBuild::VERSION;
     details.group = "python";
 
@@ -136,20 +136,8 @@ class PythonCreator : public minifi::core::CoreComponent {
     return python_package;
   }
 
-  std::string getPath(const std::string &pythonscript) {
-    return std::filesystem::path(pythonscript).parent_path().string();
-  }
-
-  std::string getFileName(const std::string &pythonscript) {
-    return std::filesystem::path(pythonscript).filename().string();
-  }
-
-  std::string getScriptName(const std::string &pythonscript) {
-    return std::filesystem::path(pythonscript).stem().string();
-  }
-
   std::vector<std::string> registered_classes_;
-  std::vector<std::string> classpaths_;
+  std::vector<std::filesystem::path> classpaths_;
 
   std::shared_ptr<core::logging::Logger> logger_ = core::logging::LoggerFactory<PythonCreator>::getLogger();
 };
diff --git a/extensions/script/python/PythonScriptEngine.cpp b/extensions/script/python/PythonScriptEngine.cpp
index 173be8e93..c18749051 100644
--- a/extensions/script/python/PythonScriptEngine.cpp
+++ b/extensions/script/python/PythonScriptEngine.cpp
@@ -44,13 +44,10 @@ void PythonScriptEngine::evaluateModuleImports() {
 
   py::eval<py::eval_statements>("import sys", *bindings_, *bindings_);
   for (const auto& module_path : module_paths_) {
-    if (std::filesystem::is_regular_file(std::filesystem::status(module_path))) {
-      std::string path;
-      std::string filename;
-      utils::file::getFileNameAndPath(module_path, path, filename);
-      py::eval<py::eval_statements>("sys.path.append(r'" + path + "')", *bindings_, *bindings_);
+    if (std::filesystem::is_regular_file(module_path)) {
+      py::eval<py::eval_statements>("sys.path.append(r'" + module_path.parent_path().string() + "')", *bindings_, *bindings_);
     } else {
-      py::eval<py::eval_statements>("sys.path.append(r'" + module_path + "')", *bindings_, *bindings_);
+      py::eval<py::eval_statements>("sys.path.append(r'" + module_path.string() + "')", *bindings_, *bindings_);
     }
   }
 }
diff --git a/extensions/script/tests/ExecutePythonProcessorTests.cpp b/extensions/script/tests/ExecutePythonProcessorTests.cpp
index 3298b48f6..708038f7a 100644
--- a/extensions/script/tests/ExecutePythonProcessorTests.cpp
+++ b/extensions/script/tests/ExecutePythonProcessorTests.cpp
@@ -34,20 +34,15 @@
 namespace {
 using org::apache::nifi::minifi::utils::putFileToDir;
 using org::apache::nifi::minifi::utils::getFileContent;
-using org::apache::nifi::minifi::utils::file::getFileNameAndPath;
-using org::apache::nifi::minifi::utils::file::concat_path;
 using org::apache::nifi::minifi::utils::file::resolve;
-using org::apache::nifi::minifi::utils::file::getFullPath;
 
 class ExecutePythonProcessorTestBase {
  public:
   ExecutePythonProcessorTestBase() :
       logTestController_(LogTestController::getInstance()),
       logger_(logging::LoggerFactory<ExecutePythonProcessorTestBase>::getLogger()) {
-    std::string path;
-    std::string filename;
-    getFileNameAndPath(__FILE__, path, filename);
-    SCRIPT_FILES_DIRECTORY = getFullPath(concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "test_python_scripts"));
+    auto path = std::filesystem::path(__FILE__).parent_path();
+    SCRIPT_FILES_DIRECTORY = minifi::utils::file::FileUtils::get_executable_dir() / "test_python_scripts";
     reInitialize();
   }
   virtual ~ExecutePythonProcessorTestBase() {
@@ -63,14 +58,14 @@ class ExecutePythonProcessorTestBase {
     logTestController_.setDebug<minifi::processors::PutFile::ReadCallback>();
   }
 
-  std::string getScriptFullPath(const std::string& script_file_name) {
+  auto getScriptFullPath(const std::filesystem::path& script_file_name) {
     return resolve(SCRIPT_FILES_DIRECTORY, script_file_name);
   }
 
   static const std::string TEST_FILE_NAME;
   static const std::string TEST_FILE_CONTENT;
 
-  std::string SCRIPT_FILES_DIRECTORY;
+  std::filesystem::path SCRIPT_FILES_DIRECTORY;
   std::unique_ptr<TestController> testController_;
   std::shared_ptr<TestPlan> plan_;
   LogTestController& logTestController_;
@@ -91,13 +86,13 @@ class SimplePythonFlowFileTransferTest : public ExecutePythonProcessorTestBase {
   void testSimpleFilePassthrough(const Expectation expectation, const core::Relationship& execute_python_out_conn, const std::string& used_as_script_file, const std::string& used_as_script_body) {
     reInitialize();
 
-    const std::string input_dir = testController_->createTempDirectory();
+    auto input_dir = testController_->createTempDirectory();
     putFileToDir(input_dir, TEST_FILE_NAME, TEST_FILE_CONTENT);
     addGetFileProcessorToPlan(input_dir);
     REQUIRE_NOTHROW(addExecutePythonProcessorToPlan(used_as_script_file, used_as_script_body));
 
-    const std::string output_dir = testController_->createTempDirectory();
-    addPutFileProcessorToPlan(execute_python_out_conn, output_dir);
+    const auto output_dir = testController_->createTempDirectory();
+    addPutFileProcessorToPlan(execute_python_out_conn, output_dir.string());
 
     plan_->runNextProcessor();  // GetFile
     if (Expectation::RUNTIME_RELATIONSHIP_EXCEPTION == expectation) {
@@ -107,7 +102,7 @@ class SimplePythonFlowFileTransferTest : public ExecutePythonProcessorTestBase {
     REQUIRE_NOTHROW(plan_->runNextProcessor());  // ExecutePythonProcessor
     plan_->runNextProcessor();  // PutFile
 
-    const std::string output_file_path = output_dir + utils::file::FileUtils::get_separator() +  TEST_FILE_NAME;
+    const auto output_file_path = output_dir / TEST_FILE_NAME;
 
     if (Expectation::OUTPUT_FILE_MATCHES_INPUT == expectation) {
       const std::string output_file_content{ getFileContent(output_file_path) };
@@ -116,10 +111,10 @@ class SimplePythonFlowFileTransferTest : public ExecutePythonProcessorTestBase {
   }
   void testsStatefulProcessor() {
     reInitialize();
-    const std::string output_dir = testController_->createTempDirectory();
+    const auto output_dir = testController_->createTempDirectory();
 
     auto executePythonProcessor = plan_->addProcessor("ExecutePythonProcessor", "executePythonProcessor");
-    plan_->setProperty(executePythonProcessor, "Script File", getScriptFullPath("stateful_processor.py"));
+    plan_->setProperty(executePythonProcessor, "Script File", getScriptFullPath("stateful_processor.py").string());
 
     addPutFileProcessorToPlan(core::Relationship("success", "description"), output_dir);
     plan_->runNextProcessor();  // ExecutePythonProcessor
@@ -130,23 +125,23 @@ class SimplePythonFlowFileTransferTest : public ExecutePythonProcessorTestBase {
     for (std::size_t i = 0; i < 10; ++i) {
       plan_->runCurrentProcessor();  // PutFile
       const std::string state_name = std::to_string(i);
-      const std::string output_file_path = concat_path(output_dir, state_name);
+      const auto output_file_path = output_dir / state_name;
       const std::string output_file_content{ getFileContent(output_file_path) };
       REQUIRE(output_file_content == state_name);
     }
   }
 
-  std::shared_ptr<core::Processor> addGetFileProcessorToPlan(const std::string& dir_path) {
+  std::shared_ptr<core::Processor> addGetFileProcessorToPlan(const std::filesystem::path& dir_path) {
     std::shared_ptr<core::Processor> getfile = plan_->addProcessor("GetFile", "getfileCreate2");
-    plan_->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir_path);
+    plan_->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir_path.string());
     plan_->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::KeepSourceFile.getName(), "true");
     return getfile;
   }
 
-  std::shared_ptr<core::Processor> addExecutePythonProcessorToPlan(const std::string& used_as_script_file, const std::string& used_as_script_body) {
+  std::shared_ptr<core::Processor> addExecutePythonProcessorToPlan(const std::filesystem::path& used_as_script_file, const std::string& used_as_script_body) {
     auto executePythonProcessor = plan_->addProcessor("ExecutePythonProcessor", "executePythonProcessor", core::Relationship("success", "description"), true);
     if (!used_as_script_file.empty()) {
-      plan_->setProperty(executePythonProcessor, "Script File", getScriptFullPath(used_as_script_file));
+      plan_->setProperty(executePythonProcessor, "Script File", getScriptFullPath(used_as_script_file).string());
     }
     if (!used_as_script_body.empty()) {
       plan_->setProperty(executePythonProcessor, "Script Body", getFileContent(getScriptFullPath(used_as_script_body)));
@@ -154,21 +149,21 @@ class SimplePythonFlowFileTransferTest : public ExecutePythonProcessorTestBase {
     return executePythonProcessor;
   }
 
-  std::shared_ptr<core::Processor> addPutFileProcessorToPlan(const core::Relationship& execute_python_outbound_connection, const std::string& dir_path) {
+  std::shared_ptr<core::Processor> addPutFileProcessorToPlan(const core::Relationship& execute_python_outbound_connection, const std::filesystem::path& dir_path) {
     std::shared_ptr<core::Processor> putfile = plan_->addProcessor("PutFile", "putfile", execute_python_outbound_connection, true);
-    plan_->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), dir_path);
+    plan_->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), dir_path.string());
     return putfile;
   }
 
   void testReloadOnScriptProperty(std::optional<bool> reload_on_script_change, uint32_t expected_success_file_count, uint32_t expected_failure_file_count) {
-    const std::string input_dir = testController_->createTempDirectory();
+    const auto input_dir = testController_->createTempDirectory();
     putFileToDir(input_dir, TEST_FILE_NAME, TEST_FILE_CONTENT);
     addGetFileProcessorToPlan(input_dir);
     auto script_content{ getFileContent(getScriptFullPath("passthrough_processor_transfering_to_success.py")) };
-    const std::string reloaded_script_dir = testController_->createTempDirectory();
+    const auto reloaded_script_dir = testController_->createTempDirectory();
     putFileToDir(reloaded_script_dir, "reloaded_script.py", script_content);
 
-    auto execute_python_processor = addExecutePythonProcessorToPlan(concat_path(reloaded_script_dir, "reloaded_script.py"), "");
+    auto execute_python_processor = addExecutePythonProcessorToPlan(reloaded_script_dir / "reloaded_script.py", "");
     if (reload_on_script_change) {
       plan_->setProperty(execute_python_processor, "Reload on Script Change", *reload_on_script_change ? "true" : "false");
     }
@@ -177,13 +172,13 @@ class SimplePythonFlowFileTransferTest : public ExecutePythonProcessorTestBase {
     plan_->addConnection(execute_python_processor, {"success", "d"}, success_putfile);
     success_putfile->setAutoTerminatedRelationships(std::array{core::Relationship{"success", "d"}, core::Relationship{"failure", "d"}});
     auto success_output_dir = testController_->createTempDirectory();
-    plan_->setProperty(success_putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), success_output_dir);
+    plan_->setProperty(success_putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), success_output_dir.string());
 
     auto failure_putfile = plan_->addProcessor("PutFile", "FailurePutFile", { {"success", "d"} }, false);
     plan_->addConnection(execute_python_processor, {"failure", "d"}, failure_putfile);
     failure_putfile->setAutoTerminatedRelationships(std::array{core::Relationship{"success", "d"}, core::Relationship{"failure", "d"}});
     auto failure_output_dir = testController_->createTempDirectory();
-    plan_->setProperty(failure_putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), failure_output_dir);
+    plan_->setProperty(failure_putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), failure_output_dir.string());
 
     testController_->runSession(plan_);
     plan_->reset();
@@ -194,8 +189,8 @@ class SimplePythonFlowFileTransferTest : public ExecutePythonProcessorTestBase {
 
     std::vector<std::string> file_contents;
 
-    auto lambda = [&file_contents](const std::string& path, const std::string& filename) -> bool {
-      std::ifstream is(path + utils::file::FileUtils::get_separator() + filename, std::ifstream::binary);
+    auto lambda = [&file_contents](const std::filesystem::path& path, const std::filesystem::path& filename) -> bool {
+      std::ifstream is(path / filename, std::ifstream::binary);
       file_contents.push_back(std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>()));
       return true;
     };
@@ -284,26 +279,26 @@ TEST_CASE_METHOD(SimplePythonFlowFileTransferTest, "Test the Reload On Script Ch
 }
 
 TEST_CASE_METHOD(SimplePythonFlowFileTransferTest, "Test module load of processor", "[executePythonProcessorModuleLoad]") {
-  const std::string input_dir = testController_->createTempDirectory();
+  const auto input_dir = testController_->createTempDirectory();
   putFileToDir(input_dir, TEST_FILE_NAME, TEST_FILE_CONTENT);
   addGetFileProcessorToPlan(input_dir);
 
   auto execute_python_processor = addExecutePythonProcessorToPlan("foo_bar_processor.py", "");
-  plan_->setProperty(execute_python_processor, "Module Directory", getScriptFullPath(concat_path("foo_modules", "foo.py")) + "," + getScriptFullPath("bar_modules"));
+  plan_->setProperty(execute_python_processor, "Module Directory", getScriptFullPath(std::filesystem::path("foo_modules")/"foo.py").string() + "," + getScriptFullPath("bar_modules").string());
 
   auto success_putfile = plan_->addProcessor("PutFile", "SuccessPutFile", { {"success", "d"} }, false);
   plan_->addConnection(execute_python_processor, {"success", "d"}, success_putfile);
   success_putfile->setAutoTerminatedRelationships(std::array{core::Relationship{"success", "d"}, core::Relationship{"failure", "d"}});
   auto success_output_dir = testController_->createTempDirectory();
-  plan_->setProperty(success_putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), success_output_dir);
+  plan_->setProperty(success_putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), success_output_dir.string());
 
   testController_->runSession(plan_);
   plan_->reset();
 
   std::vector<std::string> file_contents;
 
-  auto lambda = [&file_contents](const std::string& path, const std::string& filename) -> bool {
-    std::ifstream is(path + utils::file::FileUtils::get_separator() + filename, std::ifstream::binary);
+  auto lambda = [&file_contents](const std::filesystem::path& path, const std::filesystem::path& filename) -> bool {
+    std::ifstream is(path / filename, std::ifstream::binary);
     file_contents.push_back(std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>()));
     return true;
   };
diff --git a/extensions/script/tests/TestExecuteScriptProcessorWithLuaScript.cpp b/extensions/script/tests/TestExecuteScriptProcessorWithLuaScript.cpp
index d7c44d19c..5fbc5195a 100644
--- a/extensions/script/tests/TestExecuteScriptProcessorWithLuaScript.cpp
+++ b/extensions/script/tests/TestExecuteScriptProcessorWithLuaScript.cpp
@@ -95,7 +95,7 @@ TEST_CASE("Lua: Test Log", "[executescriptLuaLog]") {
   )");
 
   auto getFileDir = testController.createTempDirectory();
-  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir);
+  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir.string());
 
   utils::putFileToDir(getFileDir, "tstFile.ext", "tempFile");
 
@@ -150,10 +150,10 @@ TEST_CASE("Lua: Test Read File", "[executescriptLuaRead]") {
   )");
 
   auto getFileDir = testController.createTempDirectory();
-  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir);
+  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir.string());
 
   auto putFileDir = testController.createTempDirectory();
-  plan->setProperty(putFile, minifi::processors::PutFile::Directory.getName(), putFileDir);
+  plan->setProperty(putFile, minifi::processors::PutFile::Directory.getName(), putFileDir.string());
 
   testController.runSession(plan, false);
 
@@ -163,9 +163,8 @@ TEST_CASE("Lua: Test Read File", "[executescriptLuaRead]") {
   REQUIRE(records.empty());
 
   std::fstream file;
-  std::stringstream ss;
-  ss << getFileDir << "/" << "tstFile.ext";
-  file.open(ss.str(), std::ios::out);
+  auto path = getFileDir / "tstFile.ext";
+  file.open(path, std::ios::out);
   file << "tempFile";
   file.close();
   plan->reset();
@@ -178,17 +177,16 @@ TEST_CASE("Lua: Test Read File", "[executescriptLuaRead]") {
   record = plan->getCurrentFlowFile();
   testController.runSession(plan, false);
 
-  unlink(ss.str().c_str());
+  std::filesystem::remove(path);
 
   REQUIRE(logTestController.contains("[info] file content: tempFile"));
 
   // Verify that file content was preserved
-  REQUIRE(!std::ifstream(ss.str()).good());
-  std::stringstream movedFile;
-  movedFile << putFileDir << "/" << "tstFile.ext";
-  REQUIRE(std::ifstream(movedFile.str()).good());
+  REQUIRE(!std::ifstream(path).good());
+  auto moved_file = putFileDir / "tstFile.ext";
+  REQUIRE(std::ifstream(moved_file).good());
 
-  file.open(movedFile.str(), std::ios::in);
+  file.open(moved_file, std::ios::in);
   std::string contents((std::istreambuf_iterator<char>(file)),
                        std::istreambuf_iterator<char>());
   REQUIRE("tempFile" == contents);
@@ -238,10 +236,10 @@ TEST_CASE("Lua: Test Write File", "[executescriptLuaWrite]") {
   )");
 
   auto getFileDir = testController.createTempDirectory();
-  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir);
+  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir.string());
 
   auto putFileDir = testController.createTempDirectory();
-  plan->setProperty(putFile, minifi::processors::PutFile::Directory.getName(), putFileDir);
+  plan->setProperty(putFile, minifi::processors::PutFile::Directory.getName(), putFileDir.string());
 
   testController.runSession(plan, false);
 
@@ -251,9 +249,8 @@ TEST_CASE("Lua: Test Write File", "[executescriptLuaWrite]") {
   REQUIRE(records.empty());
 
   std::fstream file;
-  std::stringstream ss;
-  ss << getFileDir << "/" << "tstFile.ext";
-  file.open(ss.str(), std::ios::out);
+  auto path = getFileDir / "tstFile.ext";
+  file.open(path, std::ios::out);
   file << "tempFile";
   file.close();
   plan->reset();
@@ -266,15 +263,14 @@ TEST_CASE("Lua: Test Write File", "[executescriptLuaWrite]") {
   record = plan->getCurrentFlowFile();
   testController.runSession(plan, false);
 
-  unlink(ss.str().c_str());
+  std::filesystem::remove(path);
 
   // Verify new content was written
-  REQUIRE(!std::ifstream(ss.str()).good());
-  std::stringstream movedFile;
-  movedFile << putFileDir << "/" << "tstFile.ext";
-  REQUIRE(std::ifstream(movedFile.str()).good());
+  REQUIRE(!std::ifstream(path).good());
+  auto moved_file = putFileDir / "tstFile.ext";
+  REQUIRE(std::ifstream(moved_file).good());
 
-  file.open(movedFile.str(), std::ios::in);
+  file.open(moved_file, std::ios::in);
   std::string contents((std::istreambuf_iterator<char>(file)),
                        std::istreambuf_iterator<char>());
   REQUIRE("hello 2" == contents);
@@ -318,7 +314,7 @@ TEST_CASE("Lua: Test Update Attribute", "[executescriptLuaUpdateAttribute]") {
   )");
 
   auto getFileDir = testController.createTempDirectory();
-  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir);
+  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir.string());
 
   utils::putFileToDir(getFileDir, "tstFile.ext", "tempFile");
 
@@ -398,8 +394,6 @@ TEST_CASE("Lua: Test Require", "[executescriptLuaRequire]") {
 }
 
 TEST_CASE("Lua: Test Module Directory property", "[executescriptLuaModuleDirectoryProperty]") {
-  using org::apache::nifi::minifi::utils::file::concat_path;
-
   TestController testController;
 
   LogTestController &logTestController = LogTestController::getInstance();
@@ -422,7 +416,7 @@ TEST_CASE("Lua: Test Module Directory property", "[executescriptLuaModuleDirecto
       (SCRIPT_FILES_DIRECTORY / "foo_modules" / "foo.lua").string() + "," + (SCRIPT_FILES_DIRECTORY / "bar_modules").string());
 
   auto getFileDir = testController.createTempDirectory();
-  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir);
+  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir.string());
 
   utils::putFileToDir(getFileDir, "tstFile.ext", "tempFile");
 
@@ -452,7 +446,7 @@ TEST_CASE("Lua: Non existent script file should throw", "[executescriptLuaNonExi
   plan->setProperty(executeScript, minifi::processors::ExecuteScript::ScriptFile.getName(), "/tmp/non-existent-file");
 
   auto getFileDir = testController.createTempDirectory();
-  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir);
+  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir.string());
 
   utils::putFileToDir(getFileDir, "tstFile.ext", "tempFile");
 
diff --git a/extensions/script/tests/TestExecuteScriptProcessorWithPythonScript.cpp b/extensions/script/tests/TestExecuteScriptProcessorWithPythonScript.cpp
index f527d3d66..b03dd0844 100644
--- a/extensions/script/tests/TestExecuteScriptProcessorWithPythonScript.cpp
+++ b/extensions/script/tests/TestExecuteScriptProcessorWithPythonScript.cpp
@@ -32,65 +32,65 @@
 #include "utils/TestUtils.h"
 
 TEST_CASE("Script engine is not set", "[executescriptMisconfiguration]") {
-  TestController testController;
-  auto plan = testController.createPlan();
+  TestController test_controller;
+  auto plan = test_controller.createPlan();
 
-  auto executeScript = plan->addProcessor("ExecuteScript", "executeScript");
+  auto execute_script = plan->addProcessor("ExecuteScript", "executeScript");
 
-  plan->setProperty(executeScript, minifi::processors::ExecuteScript::ScriptEngine.getName(), "");
-  plan->setProperty(executeScript, minifi::processors::ExecuteScript::ScriptFile.getName(), "/path/to/script.py");
+  plan->setProperty(execute_script, minifi::processors::ExecuteScript::ScriptEngine.getName(), "");
+  plan->setProperty(execute_script, minifi::processors::ExecuteScript::ScriptFile.getName(), "/path/to/script.py");
 
-  REQUIRE_THROWS_AS(testController.runSession(plan, true), minifi::Exception);
+  REQUIRE_THROWS_AS(test_controller.runSession(plan, true), minifi::Exception);
 }
 
 TEST_CASE("Neither script body nor script file is set", "[executescriptMisconfiguration]") {
-  TestController testController;
-  auto plan = testController.createPlan();
+  TestController test_controller;
+  auto plan = test_controller.createPlan();
 
-  auto executeScript = plan->addProcessor("ExecuteScript", "executeScript");
+  auto execute_script = plan->addProcessor("ExecuteScript", "executeScript");
 
-  plan->setProperty(executeScript, minifi::processors::ExecuteScript::ScriptEngine.getName(), "python");
+  plan->setProperty(execute_script, minifi::processors::ExecuteScript::ScriptEngine.getName(), "python");
 
-  REQUIRE_THROWS_AS(testController.runSession(plan, true), minifi::Exception);
+  REQUIRE_THROWS_AS(test_controller.runSession(plan, true), minifi::Exception);
 }
 
 TEST_CASE("Test both script body and script file set", "[executescriptMisconfiguration]") {
-  TestController testController;
-  auto plan = testController.createPlan();
+  TestController test_controller;
+  auto plan = test_controller.createPlan();
 
-  auto executeScript = plan->addProcessor("ExecuteScript", "executeScript");
+  auto execute_script = plan->addProcessor("ExecuteScript", "executeScript");
 
-  plan->setProperty(executeScript, minifi::processors::ExecuteScript::ScriptEngine.getName(), "python");
-  plan->setProperty(executeScript, minifi::processors::ExecuteScript::ScriptFile.getName(), "/path/to/script.py");
-  plan->setProperty(executeScript, minifi::processors::ExecuteScript::ScriptBody.getName(), R"(
+  plan->setProperty(execute_script, minifi::processors::ExecuteScript::ScriptEngine.getName(), "python");
+  plan->setProperty(execute_script, minifi::processors::ExecuteScript::ScriptFile.getName(), "/path/to/script.py");
+  plan->setProperty(execute_script, minifi::processors::ExecuteScript::ScriptBody.getName(), R"(
     def onTrigger(context, session):
       log.info('hello from python')
   )");
 
-  REQUIRE_THROWS_AS(testController.runSession(plan, true), minifi::Exception);
+  REQUIRE_THROWS_AS(test_controller.runSession(plan, true), minifi::Exception);
 }
 
 TEST_CASE("Python: Test Read File", "[executescriptPythonRead]") {
-  TestController testController;
+  TestController test_controller;
 
-  LogTestController &logTestController = LogTestController::getInstance();
-  logTestController.setDebug<TestPlan>();
-  logTestController.setDebug<minifi::processors::LogAttribute>();
-  logTestController.setDebug<minifi::processors::ExecuteScript>();
+  LogTestController& log_test_controller = LogTestController::getInstance();
+  log_test_controller.setDebug<TestPlan>();
+  log_test_controller.setDebug<minifi::processors::LogAttribute>();
+  log_test_controller.setDebug<minifi::processors::ExecuteScript>();
 
-  auto plan = testController.createPlan();
+  auto plan = test_controller.createPlan();
 
-  auto getFile = plan->addProcessor("GetFile", "getFile");
-  auto logAttribute = plan->addProcessor("LogAttribute", "logAttribute",
+  auto get_file = plan->addProcessor("GetFile", "getFile");
+  auto log_attribute = plan->addProcessor("LogAttribute", "logAttribute",
                                          core::Relationship("success", "description"),
                                          true);
-  auto executeScript = plan->addProcessor("ExecuteScript",
+  auto execute_script = plan->addProcessor("ExecuteScript",
                                           "executeScript",
                                           core::Relationship("success", "description"),
                                           true);
-  auto putFile = plan->addProcessor("PutFile", "putFile", core::Relationship("success", "description"), true);
+  auto put_file = plan->addProcessor("PutFile", "putFile", core::Relationship("success", "description"), true);
 
-  plan->setProperty(executeScript, minifi::processors::ExecuteScript::ScriptBody.getName(), R"(
+  plan->setProperty(execute_script, minifi::processors::ExecuteScript::ScriptBody.getName(), R"(
     import codecs
 
     class ReadCallback(object):
@@ -108,13 +108,13 @@ TEST_CASE("Python: Test Read File", "[executescriptPythonRead]") {
         session.transfer(flow_file, REL_SUCCESS)
   )");
 
-  auto getFileDir = testController.createTempDirectory();
-  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir);
+  auto get_file_dir = test_controller.createTempDirectory();
+  plan->setProperty(get_file, minifi::processors::GetFile::Directory.getName(), get_file_dir.string());
 
-  auto putFileDir = testController.createTempDirectory();
-  plan->setProperty(putFile, minifi::processors::PutFile::Directory.getName(), putFileDir);
+  auto put_file_dir = test_controller.createTempDirectory();
+  plan->setProperty(put_file, minifi::processors::PutFile::Directory.getName(), put_file_dir.string());
 
-  testController.runSession(plan, false);
+  test_controller.runSession(plan, false);
 
   auto records = plan->getProvenanceRecords();
   std::shared_ptr<core::FlowFile> record = plan->getCurrentFlowFile();
@@ -122,60 +122,58 @@ TEST_CASE("Python: Test Read File", "[executescriptPythonRead]") {
   REQUIRE(records.empty());
 
   std::fstream file;
-  std::stringstream ss;
-  ss << getFileDir << "/" << "tstFile.ext";
-  file.open(ss.str(), std::ios::out);
+  auto path = get_file_dir / "tstFile.ext";
+  file.open(path, std::ios::out);
   file << "tempFile";
   file.close();
   plan->reset();
 
-  testController.runSession(plan, false);
-  testController.runSession(plan, false);
-  testController.runSession(plan, false);
+  test_controller.runSession(plan, false);
+  test_controller.runSession(plan, false);
+  test_controller.runSession(plan, false);
 
   records = plan->getProvenanceRecords();
   record = plan->getCurrentFlowFile();
-  testController.runSession(plan, false);
+  test_controller.runSession(plan, false);
 
-  unlink(ss.str().c_str());
+  std::filesystem::remove(path);
 
-  REQUIRE(logTestController.contains("[info] file content: tempFile"));
+  REQUIRE(log_test_controller.contains("[info] file content: tempFile"));
 
   // Verify that file content was preserved
-  REQUIRE(!std::ifstream(ss.str()).good());
-  std::stringstream movedFile;
-  movedFile << putFileDir << "/" << "tstFile.ext";
-  REQUIRE(std::ifstream(movedFile.str()).good());
+  REQUIRE(!std::ifstream(path).good());
+  auto moved_file = put_file_dir / "tstFile.ext";
+  REQUIRE(std::ifstream(moved_file).good());
 
-  file.open(movedFile.str(), std::ios::in);
+  file.open(moved_file, std::ios::in);
   std::string contents((std::istreambuf_iterator<char>(file)),
                        std::istreambuf_iterator<char>());
   REQUIRE("tempFile" == contents);
   file.close();
-  logTestController.reset();
+  log_test_controller.reset();
 }
 
 TEST_CASE("Python: Test Write File", "[executescriptPythonWrite]") {
-  TestController testController;
+  TestController test_controller;
 
-  LogTestController &logTestController = LogTestController::getInstance();
-  logTestController.setDebug<TestPlan>();
-  logTestController.setDebug<minifi::processors::LogAttribute>();
-  logTestController.setDebug<minifi::processors::ExecuteScript>();
+  LogTestController& log_test_controller = LogTestController::getInstance();
+  log_test_controller.setDebug<TestPlan>();
+  log_test_controller.setDebug<minifi::processors::LogAttribute>();
+  log_test_controller.setDebug<minifi::processors::ExecuteScript>();
 
-  auto plan = testController.createPlan();
+  auto plan = test_controller.createPlan();
 
-  auto getFile = plan->addProcessor("GetFile", "getFile");
-  auto logAttribute = plan->addProcessor("LogAttribute", "logAttribute",
+  auto get_file = plan->addProcessor("GetFile", "getFile");
+  auto log_attribute = plan->addProcessor("LogAttribute", "logAttribute",
                                          core::Relationship("success", "description"),
                                          true);
-  auto executeScript = plan->addProcessor("ExecuteScript",
+  auto execute_script = plan->addProcessor("ExecuteScript",
                                           "executeScript",
                                           core::Relationship("success", "description"),
                                           true);
   auto putFile = plan->addProcessor("PutFile", "putFile", core::Relationship("success", "description"), true);
 
-  plan->setProperty(executeScript, minifi::processors::ExecuteScript::ScriptBody.getName(), R"(
+  plan->setProperty(execute_script, minifi::processors::ExecuteScript::ScriptBody.getName(), R"(
     class WriteCallback(object):
       def process(self, output_stream):
         new_content = 'hello 2'.encode('utf-8')
@@ -190,13 +188,13 @@ TEST_CASE("Python: Test Write File", "[executescriptPythonWrite]") {
         session.transfer(flow_file, REL_SUCCESS)
   )");
 
-  auto getFileDir = testController.createTempDirectory();
-  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir);
+  auto get_file_dir = test_controller.createTempDirectory();
+  plan->setProperty(get_file, minifi::processors::GetFile::Directory.getName(), get_file_dir.string());
 
-  auto putFileDir = testController.createTempDirectory();
-  plan->setProperty(putFile, minifi::processors::PutFile::Directory.getName(), putFileDir);
+  auto put_file_dir = test_controller.createTempDirectory();
+  plan->setProperty(putFile, minifi::processors::PutFile::Directory.getName(), put_file_dir.string());
 
-  testController.runSession(plan, false);
+  test_controller.runSession(plan, false);
 
   auto records = plan->getProvenanceRecords();
   std::shared_ptr<core::FlowFile> record = plan->getCurrentFlowFile();
@@ -204,45 +202,43 @@ TEST_CASE("Python: Test Write File", "[executescriptPythonWrite]") {
   REQUIRE(records.empty());
 
   std::fstream file;
-  std::stringstream ss;
-  ss << getFileDir << "/" << "tstFile.ext";
-  file.open(ss.str(), std::ios::out);
+  auto path = get_file_dir / "tstFile.ext";
+  file.open(path, std::ios::out);
   file << "tempFile";
   file.close();
   plan->reset();
 
-  testController.runSession(plan, false);
-  testController.runSession(plan, false);
-  testController.runSession(plan, false);
+  test_controller.runSession(plan, false);
+  test_controller.runSession(plan, false);
+  test_controller.runSession(plan, false);
 
   records = plan->getProvenanceRecords();
   record = plan->getCurrentFlowFile();
-  testController.runSession(plan, false);
+  test_controller.runSession(plan, false);
 
-  unlink(ss.str().c_str());
+  std::filesystem::remove(path);
 
   // Verify new content was written
-  REQUIRE(!std::ifstream(ss.str()).good());
-  std::stringstream movedFile;
-  movedFile << putFileDir << "/" << "tstFile.ext";
-  REQUIRE(std::ifstream(movedFile.str()).good());
+  REQUIRE(!std::ifstream(path).good());
+  auto moved_file = put_file_dir / "tstFile.ext";
+  REQUIRE(std::ifstream(moved_file).good());
 
-  file.open(movedFile.str(), std::ios::in);
+  file.open(moved_file, std::ios::in);
   std::string contents((std::istreambuf_iterator<char>(file)),
                        std::istreambuf_iterator<char>());
   REQUIRE("hello 2" == contents);
   file.close();
-  logTestController.reset();
+  log_test_controller.reset();
 }
 
 TEST_CASE("Python: Test Create", "[executescriptPythonCreate]") {
-  TestController testController;
+  TestController test_controller;
 
-  LogTestController &logTestController = LogTestController::getInstance();
-  logTestController.setDebug<TestPlan>();
-  logTestController.setDebug<minifi::processors::ExecuteScript>();
+  LogTestController& log_test_controller = LogTestController::getInstance();
+  log_test_controller.setDebug<TestPlan>();
+  log_test_controller.setDebug<minifi::processors::ExecuteScript>();
 
-  auto plan = testController.createPlan();
+  auto plan = test_controller.createPlan();
 
   auto executeScript = plan->addProcessor("ExecuteScript",
                                           "executeScript");
@@ -256,22 +252,22 @@ TEST_CASE("Python: Test Create", "[executescriptPythonCreate]") {
         session.transfer(flow_file, REL_SUCCESS)
   )");
 
-  testController.runSession(plan, false);
+  test_controller.runSession(plan, false);
 
   REQUIRE(LogTestController::getInstance().contains("[info] created flow file:"));
 
-  logTestController.reset();
+  log_test_controller.reset();
 }
 
 TEST_CASE("Python: Test Update Attribute", "[executescriptPythonUpdateAttribute]") {
-  TestController testController;
+  TestController test_controller;
 
-  LogTestController &logTestController = LogTestController::getInstance();
-  logTestController.setDebug<TestPlan>();
-  logTestController.setDebug<minifi::processors::LogAttribute>();
-  logTestController.setDebug<minifi::processors::ExecuteScript>();
+  LogTestController& log_test_controller = LogTestController::getInstance();
+  log_test_controller.setDebug<TestPlan>();
+  log_test_controller.setDebug<minifi::processors::LogAttribute>();
+  log_test_controller.setDebug<minifi::processors::ExecuteScript>();
 
-  auto plan = testController.createPlan();
+  auto plan = test_controller.createPlan();
 
   auto getFile = plan->addProcessor("GetFile", "getFile");
   auto executeScript = plan->addProcessor("ExecuteScript",
@@ -295,107 +291,106 @@ TEST_CASE("Python: Test Update Attribute", "[executescriptPythonUpdateAttribute]
         session.transfer(flow_file, REL_SUCCESS)
   )");
 
-  auto getFileDir = testController.createTempDirectory();
-  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir);
+  auto get_file_dir = test_controller.createTempDirectory();
+  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), get_file_dir.string());
 
-  utils::putFileToDir(getFileDir, "tstFile.ext", "tempFile");
+  utils::putFileToDir(get_file_dir, "tstFile.ext", "tempFile");
 
-  testController.runSession(plan, false);
-  testController.runSession(plan, false);
-  testController.runSession(plan, false);
+  test_controller.runSession(plan, false);
+  test_controller.runSession(plan, false);
+  test_controller.runSession(plan, false);
 
   REQUIRE(LogTestController::getInstance().contains("key:test_attr value:2"));
 
-  logTestController.reset();
+  log_test_controller.reset();
 }
 
 TEST_CASE("Python: Test Get Context Property", "[executescriptPythonGetContextProperty]") {
-  TestController testController;
+  TestController test_controller;
 
-  LogTestController &logTestController = LogTestController::getInstance();
-  logTestController.setDebug<TestPlan>();
-  logTestController.setDebug<minifi::processors::LogAttribute>();
-  logTestController.setDebug<minifi::processors::ExecuteScript>();
+  LogTestController& log_test_controller = LogTestController::getInstance();
+  log_test_controller.setDebug<TestPlan>();
+  log_test_controller.setDebug<minifi::processors::LogAttribute>();
+  log_test_controller.setDebug<minifi::processors::ExecuteScript>();
 
-  auto plan = testController.createPlan();
+  auto plan = test_controller.createPlan();
 
-  auto getFile = plan->addProcessor("GetFile", "getFile");
-  auto executeScript = plan->addProcessor("ExecuteScript",
+  auto get_file = plan->addProcessor("GetFile", "getFile");
+  auto execute_script = plan->addProcessor("ExecuteScript",
                                           "executeScript",
                                           core::Relationship("success", "description"),
                                           true);
-  auto logAttribute = plan->addProcessor("LogAttribute", "logAttribute",
+  auto log_attribute = plan->addProcessor("LogAttribute", "logAttribute",
                                          core::Relationship("success", "description"),
                                          true);
 
-  plan->setProperty(executeScript, minifi::processors::ExecuteScript::ScriptBody.getName(), R"(
+  plan->setProperty(execute_script, minifi::processors::ExecuteScript::ScriptBody.getName(), R"(
     def onTrigger(context, session):
       script_engine = context.getProperty('Script Engine')
       log.info('got Script Engine property: %s' % script_engine)
   )");
 
-  auto getFileDir = testController.createTempDirectory();
-  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir);
+  auto get_file_dir = test_controller.createTempDirectory();
+  plan->setProperty(get_file, minifi::processors::GetFile::Directory.getName(), get_file_dir.string());
 
-  utils::putFileToDir(getFileDir, "tstFile.ext", "tempFile");
+  utils::putFileToDir(get_file_dir, "tstFile.ext", "tempFile");
 
-  testController.runSession(plan, false);
-  testController.runSession(plan, false);
-  testController.runSession(plan, false);
+  test_controller.runSession(plan, false);
+  test_controller.runSession(plan, false);
+  test_controller.runSession(plan, false);
 
   REQUIRE(LogTestController::getInstance().contains("[info] got Script Engine property: python"));
 
-  logTestController.reset();
+  log_test_controller.reset();
 }
 
 TEST_CASE("Python: Test Module Directory property", "[executescriptPythonModuleDirectoryProperty]") {
-  using org::apache::nifi::minifi::utils::file::concat_path;
   using org::apache::nifi::minifi::utils::file::get_executable_dir;
 
-  TestController testController;
+  TestController test_controller;
 
-  LogTestController &logTestController = LogTestController::getInstance();
-  logTestController.setDebug<TestPlan>();
-  logTestController.setDebug<minifi::processors::ExecuteScript>();
+  LogTestController& log_test_controller = LogTestController::getInstance();
+  log_test_controller.setDebug<TestPlan>();
+  log_test_controller.setDebug<minifi::processors::ExecuteScript>();
 
-  const std::string SCRIPT_FILES_DIRECTORY = minifi::utils::file::getFullPath(concat_path(minifi::utils::file::FileUtils::get_executable_dir(), "test_python_scripts"));
-  auto getScriptFullPath = [&SCRIPT_FILES_DIRECTORY](const std::string& script_file_name) {
-    return concat_path(SCRIPT_FILES_DIRECTORY, script_file_name);
-  };
+  const auto script_files_directory = std::filesystem::path(__FILE__).parent_path() / "test_python_scripts";
 
-  auto plan = testController.createPlan();
+  auto plan = test_controller.createPlan();
 
-  auto getFile = plan->addProcessor("GetFile", "getFile");
-  auto executeScript = plan->addProcessor("ExecuteScript",
+  auto get_file = plan->addProcessor("GetFile", "getFile");
+  auto execute_script = plan->addProcessor("ExecuteScript",
                                           "executeScript",
                                           core::Relationship("success", "description"),
                                           true);
 
-  plan->setProperty(executeScript, minifi::processors::ExecuteScript::ScriptFile.getName(), getScriptFullPath("foo_bar_processor.py"));
-  plan->setProperty(executeScript, minifi::processors::ExecuteScript::ModuleDirectory.getName(), getScriptFullPath(concat_path("foo_modules", "foo.py")) + "," + getScriptFullPath("bar_modules"));
+  plan->setProperty(execute_script, minifi::processors::ExecuteScript::ScriptEngine.getName(), "python");
+  plan->setProperty(execute_script, minifi::processors::ExecuteScript::ScriptFile.getName(), (script_files_directory / "foo_bar_processor.py").string());
+  plan->setProperty(execute_script,
+                    minifi::processors::ExecuteScript::ModuleDirectory.getName(),
+                    (script_files_directory / "foo_modules" / "foo.py").string() + "," + (script_files_directory / "bar_modules").string());
 
-  auto getFileDir = testController.createTempDirectory();
-  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir);
+  auto get_file_dir = test_controller.createTempDirectory();
+  plan->setProperty(get_file, minifi::processors::GetFile::Directory.getName(), get_file_dir.string());
 
-  utils::putFileToDir(getFileDir, "tstFile.ext", "tempFile");
+  utils::putFileToDir(get_file_dir, "tstFile.ext", "tempFile");
 
-  testController.runSession(plan, true);
+  test_controller.runSession(plan, true);
 
   REQUIRE(LogTestController::getInstance().contains("foobar"));
 
-  logTestController.reset();
+  log_test_controller.reset();
 }
 
 TEST_CASE("Python: Non existent script file should throw", "[executescriptPythonNonExistentScriptFile]") {
-  TestController testController;
+  TestController test_controller;
 
-  LogTestController &logTestController = LogTestController::getInstance();
-  logTestController.setDebug<TestPlan>();
-  logTestController.setDebug<minifi::processors::ExecuteScript>();
+  LogTestController& log_test_controller = LogTestController::getInstance();
+  log_test_controller.setDebug<TestPlan>();
+  log_test_controller.setDebug<minifi::processors::ExecuteScript>();
 
-  auto plan = testController.createPlan();
+  auto plan = test_controller.createPlan();
 
-  auto getFile = plan->addProcessor("GetFile", "getFile");
+  auto get_file = plan->addProcessor("GetFile", "getFile");
   auto executeScript = plan->addProcessor("ExecuteScript",
                                           "executeScript",
                                           core::Relationship("success", "description"),
@@ -403,12 +398,12 @@ TEST_CASE("Python: Non existent script file should throw", "[executescriptPython
 
   plan->setProperty(executeScript, minifi::processors::ExecuteScript::ScriptFile.getName(), "/tmp/non-existent-file");
 
-  auto getFileDir = testController.createTempDirectory();
-  plan->setProperty(getFile, minifi::processors::GetFile::Directory.getName(), getFileDir);
+  auto get_file_dir = test_controller.createTempDirectory();
+  plan->setProperty(get_file, minifi::processors::GetFile::Directory.getName(), get_file_dir.string());
 
-  utils::putFileToDir(getFileDir, "tstFile.ext", "tempFile");
+  utils::putFileToDir(get_file_dir, "tstFile.ext", "tempFile");
 
-  REQUIRE_THROWS_AS(testController.runSession(plan), minifi::Exception);
+  REQUIRE_THROWS_AS(test_controller.runSession(plan), minifi::Exception);
 
-  logTestController.reset();
+  log_test_controller.reset();
 }
diff --git a/extensions/sftp/processors/FetchSFTP.cpp b/extensions/sftp/processors/FetchSFTP.cpp
index 45b61389c..8872b8cc0 100644
--- a/extensions/sftp/processors/FetchSFTP.cpp
+++ b/extensions/sftp/processors/FetchSFTP.cpp
@@ -87,12 +87,15 @@ void FetchSFTP::onTrigger(const std::shared_ptr<core::ProcessContext> &context,
     return;
   }
 
-  /* Parse processor-specific properties */
-  std::string remote_file;
-  std::string move_destination_directory;
+  std::filesystem::path remote_file;
+  if (auto remote_file_str = context->getProperty(RemoteFile, flow_file)) {
+    remote_file = std::filesystem::path(*remote_file_str, std::filesystem::path::format::generic_format);
+  }
 
-  context->getProperty(RemoteFile, remote_file, flow_file);
-  context->getProperty(MoveDestinationDirectory, move_destination_directory, flow_file);
+  std::filesystem::path move_destination_directory;
+  if (auto move_destination_directory_str = context->getProperty(MoveDestinationDirectory, flow_file)) {
+    move_destination_directory = std::filesystem::path(*move_destination_directory_str, std::filesystem::path::format::generic_format);
+  }
 
   /* Get SFTPClient from cache or create it */
   const SFTPProcessorBase::ConnectionCacheKey connection_cache_key = {common_properties.hostname,
@@ -123,7 +126,7 @@ void FetchSFTP::onTrigger(const std::shared_ptr<core::ProcessContext> &context,
   /* Download file */
   try {
     session->write(flow_file, [&remote_file, &client](const std::shared_ptr<io::OutputStream>& stream) -> int64_t {
-      auto bytes_read = client->getFile(remote_file, *stream);
+      auto bytes_read = client->getFile(remote_file.generic_string(), *stream);
       if (!bytes_read) {
         throw utils::SFTPException{client->getLastError()};
       }
@@ -151,37 +154,35 @@ void FetchSFTP::onTrigger(const std::shared_ptr<core::ProcessContext> &context,
   }
 
   /* Set attributes */
-  std::string parent_path;
-  std::string child_path;
-  std::tie(parent_path, child_path) = utils::file::split_path(remote_file, true /*force_posix*/);
+  std::string child_path = remote_file.filename().generic_string();
 
   session->putAttribute(flow_file, ATTRIBUTE_SFTP_REMOTE_HOST, common_properties.hostname);
   session->putAttribute(flow_file, ATTRIBUTE_SFTP_REMOTE_PORT, std::to_string(common_properties.port));
-  session->putAttribute(flow_file, ATTRIBUTE_SFTP_REMOTE_FILENAME, remote_file);
+  session->putAttribute(flow_file, ATTRIBUTE_SFTP_REMOTE_FILENAME, remote_file.generic_string());
   flow_file->setAttribute(core::SpecialFlowAttribute::FILENAME, child_path);
-  if (!parent_path.empty()) {
-    flow_file->setAttribute(core::SpecialFlowAttribute::PATH, parent_path);
+  if (!remote_file.parent_path().empty()) {
+    flow_file->setAttribute(core::SpecialFlowAttribute::PATH, (remote_file.parent_path() / "").generic_string());
   }
 
   /* Execute completion strategy */
   if (completion_strategy_ == COMPLETION_STRATEGY_DELETE_FILE) {
-    if (!client->removeFile(remote_file)) {
-      logger_->log_warn("Completion Strategy is Delete File, but failed to delete remote file \"%s\"", remote_file);
+    if (!client->removeFile(remote_file.generic_string())) {
+      logger_->log_warn("Completion Strategy is Delete File, but failed to delete remote file \"%s\"", remote_file.generic_string());
     }
   } else if (completion_strategy_ == COMPLETION_STRATEGY_MOVE_FILE) {
     bool should_move = true;
     if (create_directory_) {
-      auto res = createDirectoryHierarchy(*client, move_destination_directory, disable_directory_listing_);
+      auto res = createDirectoryHierarchy(*client, move_destination_directory.generic_string(), disable_directory_listing_);
       if (res != SFTPProcessorBase::CreateDirectoryHierarchyError::CREATE_DIRECTORY_HIERARCHY_ERROR_OK) {
         should_move = false;
       }
     }
     if (!should_move) {
-      logger_->log_warn("Completion Strategy is Move File, but failed to create Move Destination Directory \"%s\"", move_destination_directory);
+      logger_->log_warn("Completion Strategy is Move File, but failed to create Move Destination Directory \"%s\"", move_destination_directory.generic_string());
     } else {
-      auto target_path = utils::file::concat_path(move_destination_directory, child_path);
-      if (!client->rename(remote_file, target_path, false /*overwrite*/)) {
-        logger_->log_warn(R"(Completion Strategy is Move File, but failed to move file "%s" to "%s")", remote_file, target_path);
+      auto target_path = move_destination_directory / child_path;
+      if (!client->rename(remote_file.generic_string(), target_path.generic_string(), false /*overwrite*/)) {
+        logger_->log_warn(R"(Completion Strategy is Move File, but failed to move file "%s" to "%s")", remote_file.generic_string(), target_path.generic_string());
       }
     }
   }
diff --git a/extensions/sftp/processors/ListSFTP.cpp b/extensions/sftp/processors/ListSFTP.cpp
index 438b7e501..2f671b56d 100644
--- a/extensions/sftp/processors/ListSFTP.cpp
+++ b/extensions/sftp/processors/ListSFTP.cpp
@@ -186,7 +186,7 @@ ListSFTP::Child::Child(const std::string& parent_path_, std::tuple<std::string /
 }
 
 std::string ListSFTP::Child::getPath() const {
-  return utils::file::FileUtils::concat_path(parent_path, filename, true /*force_posix*/);
+  return (parent_path / filename).generic_string();
 }
 
 bool ListSFTP::filter(const std::string& parent_path, const std::tuple<std::string /* filename */, std::string /* longentry */, LIBSSH2_SFTP_ATTRIBUTES /* attrs */>& sftp_child) {
@@ -290,7 +290,7 @@ bool ListSFTP::filterDirectory(const std::string& parent_path, const std::string
 
   /* Path Filter Regex */
   if (compiled_path_filter_regex_) {
-    std::string dir_path = utils::file::FileUtils::concat_path(parent_path, filename, true /*force_posix*/);
+    std::string dir_path = utils::StringUtils::join_pack(parent_path, "/", filename);
     bool match = false;
     match = utils::regexMatch(dir_path, *compiled_path_filter_regex_);
     if (!match) {
@@ -344,8 +344,8 @@ bool ListSFTP::createAndTransferFlowFileFromChild(
   /* mtime */
   session->putAttribute(flow_file, ATTRIBUTE_FILE_LASTMODIFIEDTIME, mtime_str);
 
-  flow_file->setAttribute(core::SpecialFlowAttribute::FILENAME, child.filename);
-  flow_file->setAttribute(core::SpecialFlowAttribute::PATH, child.parent_path);
+  flow_file->setAttribute(core::SpecialFlowAttribute::FILENAME, child.filename.generic_string());
+  flow_file->setAttribute(core::SpecialFlowAttribute::PATH, child.parent_path.generic_string());
 
   session->transfer(flow_file, Success);
 
@@ -584,7 +584,7 @@ void ListSFTP::listByTrackingTimestamps(
         if (createAndTransferFlowFileFromChild(session, hostname, port, username, file)) {
           flow_files_created++;
         } else {
-          logger_->log_error("Failed to emit FlowFile for \"%s\"", file.filename);
+          logger_->log_error("Failed to emit FlowFile for \"%s\"", file.filename.generic_string());
           context->yield();
           return;
         }
@@ -831,16 +831,16 @@ void ListSFTP::onTrigger(const std::shared_ptr<core::ProcessContext> &context, c
     return;
   }
 
-  /* Parse processor-specific properties */
-  std::string remote_path;
-  std::chrono::milliseconds entity_tracking_time_window = 3h;  /* The default is 3 hours */
-
-  std::string value;
-  context->getProperty(RemotePath.getName(), remote_path);
+  std::string remote_path_str;
+  context->getProperty(RemotePath.getName(), remote_path_str);
   /* Remove trailing slashes */
-  while (remote_path.size() > 1U && remote_path.back() == '/') {
-    remote_path.resize(remote_path.size() - 1);
+  while (remote_path_str.size() > 1 && remote_path_str.ends_with('/')) {
+    remote_path_str.pop_back();
   }
+  std::filesystem::path remote_path{remote_path_str, std::filesystem::path::format::generic_format};
+
+  std::string value;
+  std::chrono::milliseconds entity_tracking_time_window = 3h;  /* The default is 3 hours */
   if (context->getProperty(EntityTrackingTimeWindow.getName(), value)) {
     if (auto parsed_entity_time_window = utils::timeutils::StringToDuration<std::chrono::milliseconds>(value)) {
       entity_tracking_time_window = parsed_entity_time_window.value();
@@ -890,7 +890,8 @@ void ListSFTP::onTrigger(const std::shared_ptr<core::ProcessContext> &context, c
 
   /* Add initial directory */
   Child root;
-  std::tie(root.parent_path, root.filename) = utils::file::FileUtils::split_path(remote_path, true /*force_posix*/);
+  root.parent_path = remote_path.parent_path();
+  root.filename = remote_path.filename();
   root.directory = true;
   directories.emplace_back(std::move(root));
 
@@ -901,7 +902,7 @@ void ListSFTP::onTrigger(const std::shared_ptr<core::ProcessContext> &context, c
 
     std::string new_parent_path;
     if (directory.parent_path.empty()) {
-      new_parent_path = directory.filename;
+      new_parent_path = directory.filename.generic_string();
     } else {
       new_parent_path = directory.getPath();
     }
@@ -923,9 +924,10 @@ void ListSFTP::onTrigger(const std::shared_ptr<core::ProcessContext> &context, c
 
   /* Process the files with the appropriate tracking strategy */
   if (listing_strategy_ == LISTING_STRATEGY_TRACKING_TIMESTAMPS) {
-    listByTrackingTimestamps(context, session, common_properties.hostname, common_properties.port, common_properties.username, remote_path, std::move(files));
+    listByTrackingTimestamps(context, session, common_properties.hostname, common_properties.port, common_properties.username, remote_path.generic_string(), std::move(files));
   } else if (listing_strategy_ == LISTING_STRATEGY_TRACKING_ENTITIES) {
-    listByTrackingEntities(context, session, common_properties.hostname, common_properties.port, common_properties.username, remote_path, entity_tracking_time_window, std::move(files));
+    listByTrackingEntities(context, session, common_properties.hostname, common_properties.port,
+        common_properties.username, remote_path.generic_string(), entity_tracking_time_window, std::move(files));
   } else {
     logger_->log_error("Unknown Listing Strategy: \"%s\"", listing_strategy_.c_str());
     context->yield();
diff --git a/extensions/sftp/processors/ListSFTP.h b/extensions/sftp/processors/ListSFTP.h
index b0cba1a18..0867aff3a 100644
--- a/extensions/sftp/processors/ListSFTP.h
+++ b/extensions/sftp/processors/ListSFTP.h
@@ -138,7 +138,7 @@ class ListSFTP : public SFTPProcessorBase {
   std::string last_listing_strategy_;
   std::string last_hostname_;
   std::string last_username_;
-  std::string last_remote_path_;
+  std::filesystem::path last_remote_path_;
 
   struct Child {
     Child();
@@ -146,8 +146,8 @@ class ListSFTP : public SFTPProcessorBase {
     [[nodiscard]] std::string getPath() const;
 
     bool directory;
-    std::string parent_path;
-    std::string filename;
+    std::filesystem::path parent_path;
+    std::filesystem::path filename;
     LIBSSH2_SFTP_ATTRIBUTES attrs;
   };
 
diff --git a/extensions/sftp/processors/PutSFTP.cpp b/extensions/sftp/processors/PutSFTP.cpp
index 39dc30f0c..7b7c99595 100644
--- a/extensions/sftp/processors/PutSFTP.cpp
+++ b/extensions/sftp/processors/PutSFTP.cpp
@@ -106,8 +106,8 @@ bool PutSFTP::processOne(const std::shared_ptr<core::ProcessContext> &context, c
   }
 
   /* Parse processor-specific properties */
-  std::string filename;
-  std::string remote_path;
+  std::filesystem::path filename;
+  std::filesystem::path remote_path;
   bool disable_directory_listing = false;
   std::string temp_file_name;
   std::optional<std::chrono::system_clock::time_point> last_modified_;
@@ -118,18 +118,18 @@ bool PutSFTP::processOne(const std::shared_ptr<core::ProcessContext> &context, c
   bool remote_group_set = false;
   uint64_t remote_group = 0U;
 
-  flow_file->getAttribute(core::SpecialFlowAttribute::FILENAME, filename);
+  if (auto file_name_str = flow_file->getAttribute(core::SpecialFlowAttribute::FILENAME))
+    filename = *file_name_str;
 
   std::string value;
-  context->getProperty(RemotePath, remote_path, flow_file);
-  /* Remove trailing slashes */
-  while (remote_path.size() > 1U && remote_path.back() == '/') {
-    remote_path.resize(remote_path.size() - 1);
-  }
-  /* Empty path means current directory, so we change it to '.' */
-  if (remote_path.empty()) {
-    remote_path = ".";
+  if (auto remote_path_str = context->getProperty(RemotePath, flow_file)) {
+    remote_path = std::filesystem::path(*remote_path_str, std::filesystem::path::format::generic_format).lexically_normal();
+    while (remote_path.filename().empty() && !remote_path.empty())
+      remote_path = remote_path.parent_path();
+    if (remote_path.empty())
+      remote_path = ".";
   }
+
   if (context->getDynamicProperty(DisableDirectoryListing.getName(), value) ||
       context->getProperty(DisableDirectoryListing.getName(), value)) {
     disable_directory_listing = utils::StringUtils::toBool(value).value_or(false);
@@ -156,7 +156,7 @@ bool PutSFTP::processOne(const std::shared_ptr<core::ProcessContext> &context, c
 
   /* Reject zero-byte files if needed */
   if (reject_zero_byte_ && flow_file->getSize() == 0U) {
-    logger_->log_debug("Rejecting %s because it is zero bytes", filename);
+    logger_->log_debug("Rejecting %s because it is zero bytes", filename.generic_string());
     session->transfer(flow_file, Reject);
     return true;
   }
@@ -188,9 +188,9 @@ bool PutSFTP::processOne(const std::shared_ptr<core::ProcessContext> &context, c
   };
 
   /* Try to detect conflicts if needed */
-  std::string resolved_filename = filename;
+  std::string resolved_filename = filename.generic_string();
   if (conflict_resolution_ != CONFLICT_RESOLUTION_NONE) {
-    std::string target_path = utils::file::concat_path(remote_path, filename, true /*force_posix*/);
+    auto target_path = (remote_path / filename).generic_string();
     LIBSSH2_SFTP_ATTRIBUTES attrs;
     if (!client->stat(target_path, true /*follow_symlinks*/, attrs)) {
       if (client->getLastError() != utils::SFTPError::FileDoesNotExist) {
@@ -225,10 +225,8 @@ bool PutSFTP::processOne(const std::shared_ptr<core::ProcessContext> &context, c
         std::string possible_resolved_filename;
         bool unique_name_generated = false;
         for (int i = 1; i < 100; i++) {
-          std::stringstream possible_resolved_filename_ss;
-          possible_resolved_filename_ss << i << "." << filename;
-          possible_resolved_filename = possible_resolved_filename_ss.str();
-          std::string possible_resolved_path = utils::file::concat_path(remote_path, possible_resolved_filename, true /*force_posix*/);
+          possible_resolved_filename = std::to_string(i) + "." + filename.generic_string();
+          auto possible_resolved_path = (remote_path / possible_resolved_filename).generic_string();
           if (!client->stat(possible_resolved_path, true /*follow_symlinks*/, attrs)) {
             if (client->getLastError() == utils::SFTPError::FileDoesNotExist) {
               unique_name_generated = true;
@@ -241,7 +239,7 @@ bool PutSFTP::processOne(const std::shared_ptr<core::ProcessContext> &context, c
           }
         }
         if (unique_name_generated) {
-          logger_->log_debug("Resolved %s to %s", filename.c_str(), possible_resolved_filename.c_str());
+          logger_->log_debug("Resolved %s to %s", filename.generic_string(), possible_resolved_filename);
           resolved_filename = std::move(possible_resolved_filename);
         } else {
           logger_->log_error("Rejecting %s because a unique name could not be determined after 99 attempts", filename.c_str());
@@ -255,7 +253,7 @@ bool PutSFTP::processOne(const std::shared_ptr<core::ProcessContext> &context, c
 
   /* Create remote directory if needed */
   if (create_directory_) {
-    auto res = createDirectoryHierarchy(*client, remote_path, disable_directory_listing);
+    auto res = createDirectoryHierarchy(*client, remote_path.generic_string(), disable_directory_listing);
     switch (res) {
       case SFTPProcessorBase::CreateDirectoryHierarchyError::CREATE_DIRECTORY_HIERARCHY_ERROR_OK:
         break;
@@ -276,22 +274,21 @@ bool PutSFTP::processOne(const std::shared_ptr<core::ProcessContext> &context, c
   }
 
   /* Upload file */
-  std::stringstream target_path_ss;
-  target_path_ss << remote_path << "/";
+  auto target_path = remote_path;
   if (!IsNullOrEmpty(temp_file_name)) {
-    target_path_ss << temp_file_name;
+    target_path /= temp_file_name;
   } else if (dot_rename_) {
-    target_path_ss << "." << resolved_filename;
+    target_path /= "." + resolved_filename;
   } else {
-    target_path_ss << resolved_filename;
+    target_path /= resolved_filename;
   }
-  auto target_path = target_path_ss.str();
-  std::string final_target_path = utils::file::concat_path(remote_path, resolved_filename, true /*force_posix*/);
+
+  std::string final_target_path = (remote_path / resolved_filename).generic_string();
   logger_->log_debug("The target path is %s, final target path is %s", target_path.c_str(), final_target_path.c_str());
 
   try {
     session->read(flow_file, [&client, &target_path, this](const std::shared_ptr<io::InputStream>& stream) {
-      if (!client->putFile(target_path,
+      if (!client->putFile(target_path.generic_string(),
           *stream,
           conflict_resolution_ == CONFLICT_RESOLUTION_REPLACE /*overwrite*/,
           stream->size() /*expected_size*/)) {
@@ -307,10 +304,10 @@ bool PutSFTP::processOne(const std::shared_ptr<core::ProcessContext> &context, c
 
   /* Move file to its final place */
   if (target_path != final_target_path) {
-    if (!client->rename(target_path, final_target_path, conflict_resolution_ == CONFLICT_RESOLUTION_REPLACE /*overwrite*/)) {
-      logger_->log_error("Failed to move temporary file %s to final path %s", target_path, final_target_path);
-      if (!client->removeFile(target_path)) {
-        logger_->log_error("Failed to remove temporary file %s", target_path.c_str());
+    if (!client->rename(target_path.generic_string(), final_target_path, conflict_resolution_ == CONFLICT_RESOLUTION_REPLACE /*overwrite*/)) {
+      logger_->log_error("Failed to move temporary file %s to final path %s", target_path.generic_string(), final_target_path);
+      if (!client->removeFile(target_path.generic_string())) {
+        logger_->log_error("Failed to remove temporary file %s", target_path.generic_string());
       }
       session->transfer(flow_file, Failure);
       return true;
@@ -348,7 +345,7 @@ bool PutSFTP::processOne(const std::shared_ptr<core::ProcessContext> &context, c
     }
     if (!client->setAttributes(final_target_path, attrs)) {
       /* This is not fatal, just log a warning */
-      logger_->log_warn("Failed to set file attributes for %s", target_path);
+      logger_->log_warn("Failed to set file attributes for %s", target_path.generic_string());
     }
   }
 
diff --git a/extensions/sftp/tests/CMakeLists.txt b/extensions/sftp/tests/CMakeLists.txt
index 721344df8..f0f3f435c 100644
--- a/extensions/sftp/tests/CMakeLists.txt
+++ b/extensions/sftp/tests/CMakeLists.txt
@@ -18,9 +18,9 @@
 #
 
 # Java is required for SFTPTestServer, we will disable tests if we can't find it
-message(STATUS "JAVA_HOME: '$ENV{JAVA_HOME}'")
 find_package(Java)
 find_package(Maven)
+message(STATUS "JAVA_HOME: '$ENV{JAVA_HOME}'")
 message(STATUS "MAVEN: ${MAVEN_EXECUTABLE}")
 
 if (NOT SKIP_TESTS AND Java_FOUND AND Maven_FOUND AND NOT DISABLE_EXPRESSION_LANGUAGE)
diff --git a/extensions/sftp/tests/FetchSFTPTests.cpp b/extensions/sftp/tests/FetchSFTPTests.cpp
index 80bc23d00..290e1866f 100644
--- a/extensions/sftp/tests/FetchSFTPTests.cpp
+++ b/extensions/sftp/tests/FetchSFTPTests.cpp
@@ -16,44 +16,22 @@
  * limitations under the License.
  */
 
-#include <sys/stat.h>
-#undef NDEBUG
-#include <cassert>
-#include <cstring>
-#include <utility>
-#include <chrono>
-#include <fstream>
 #include <memory>
 #include <string>
 #include <thread>
 #include <type_traits>
 #include <vector>
 #include <iostream>
-#include <sstream>
-#include <algorithm>
-#include <functional>
 #include <iterator>
-#include <random>
-#ifndef WIN32
-#include <unistd.h>
-#endif
 
 #include "TestBase.h"
 #include "Catch.h"
-#include "utils/StringUtils.h"
 #include "utils/file/FileUtils.h"
-#include "core/Core.h"
-#include "core/logging/Logger.h"
 #include "core/ProcessGroup.h"
-#include "core/yaml/YamlConfiguration.h"
 #include "FlowController.h"
-#include "properties/Configure.h"
-#include "unit/ProvenanceTestHelper.h"
-#include "io/StreamFactory.h"
 #include "processors/FetchSFTP.h"
 #include "processors/GenerateFlowFile.h"
 #include "processors/LogAttribute.h"
-#include "processors/UpdateAttribute.h"
 #include "processors/PutFile.h"
 #include "tools/SFTPTestServer.h"
 
@@ -122,7 +100,7 @@ class FetchSFTPTestsFixture {
     plan->setProperty(fetch_sftp, "Use Compression", "false");
 
     // Configure PutFile processor
-    plan->setProperty(put_file, "Directory", dst_dir + "/${path}");
+    plan->setProperty(put_file, "Directory", (dst_dir / "${path}").string());
     plan->setProperty(put_file, "Conflict Resolution Strategy", minifi::processors::PutFile::CONFLICT_RESOLUTION_STRATEGY_FAIL);
     plan->setProperty(put_file, "Create Missing Directories", "true");
   }
@@ -133,11 +111,11 @@ class FetchSFTPTestsFixture {
 
   // Create source file
   void createFile(const std::string& relative_path, const std::string& content) {
+    const auto file_path = src_dir / "vfs" / relative_path;
+    std::filesystem::create_directories(file_path.parent_path());
+
     std::fstream file;
-    std::stringstream ss;
-    ss << src_dir << "/vfs/" << relative_path;
-    utils::file::create_dir(utils::file::get_parent_path(ss.str()));
-    file.open(ss.str(), std::ios::out);
+    file.open(file_path, std::ios::out);
     file << content;
     file.close();
   }
@@ -147,48 +125,31 @@ class FetchSFTPTestsFixture {
     IN_SOURCE
   };
 
-  void testFile(TestWhere where, const std::string& relative_path, const std::string& expected_content) {
-    std::stringstream resultFile;
-    if (where == IN_DESTINATION) {
-      resultFile << dst_dir << "/" << relative_path;
-    } else {
-      resultFile << src_dir << "/vfs/" << relative_path;
-#ifndef WIN32
-      /* Workaround for mina-sshd setting the read file's permissions to 0000 */
-      REQUIRE(0 == chmod(resultFile.str().c_str(), 0644));
-#endif
-    }
-    std::ifstream file(resultFile.str());
-    REQUIRE(true == file.good());
+  void testFile(TestWhere where, const std::filesystem::path& relative_path, std::string_view expected_content) {
+    std::filesystem::path expected_path = where == IN_DESTINATION ? dst_dir / relative_path : src_dir / "vfs" / relative_path;
+    REQUIRE(std::filesystem::exists(expected_path));
+    std::filesystem::permissions(expected_path, static_cast<std::filesystem::perms>(0644));
+
+    std::ifstream file(expected_path);
+    REQUIRE(file.good());
     std::stringstream content;
     std::vector<char> buffer(1024U);
     while (file) {
       file.read(buffer.data(), buffer.size());
       content << std::string(buffer.data(), file.gcount());
     }
-    REQUIRE(expected_content == content.str());
+    CHECK(expected_content == content.str());
   }
 
   void testFileNotExists(TestWhere where, const std::string& relative_path) {
-    std::stringstream resultFile;
-    if (where == IN_DESTINATION) {
-      resultFile << dst_dir << "/" << relative_path;
-    } else {
-      resultFile << src_dir << "/vfs/" << relative_path;
-#ifndef WIN32
-      /* Workaround for mina-sshd setting the read file's permissions to 0000 */
-      REQUIRE(-1 == chmod(resultFile.str().c_str(), 0644));
-#endif
-    }
-    std::ifstream file(resultFile.str());
-    REQUIRE(false == file.is_open());
-    REQUIRE(false == file.good());
+    std::filesystem::path expected_path = where == IN_DESTINATION ? dst_dir / relative_path : src_dir / "vfs" / relative_path;
+    CHECK(!std::filesystem::exists(expected_path));
   }
 
  protected:
   TestController testController;
-  std::string src_dir = testController.createTempDirectory();
-  std::string dst_dir = testController.createTempDirectory();
+  std::filesystem::path src_dir{testController.createTempDirectory()};
+  std::filesystem::path dst_dir{testController.createTempDirectory()};
   std::shared_ptr<TestPlan> plan = testController.createPlan();
   std::unique_ptr<SFTPTestServer> sftp_server;
   std::shared_ptr<core::Processor> generate_flow_file;
@@ -217,7 +178,7 @@ TEST_CASE_METHOD(FetchSFTPTestsFixture, "FetchSFTP fetch one file", "[FetchSFTP]
 
 TEST_CASE_METHOD(FetchSFTPTestsFixture, "FetchSFTP public key authentication", "[FetchSFTP][basic]") {
   plan->setProperty(fetch_sftp, "Remote File", "nifi_test/tstFile.ext");
-  plan->setProperty(fetch_sftp, "Private Key Path", utils::file::concat_path(get_sftp_test_dir(), "resources/id_rsa"));
+  plan->setProperty(fetch_sftp, "Private Key Path", (get_sftp_test_dir() / "resources" / "id_rsa").generic_string());
   plan->setProperty(fetch_sftp, "Private Key Passphrase", "privatekeypassword");
 
   createFile("nifi_test/tstFile.ext", "Test content 1");
@@ -256,7 +217,7 @@ TEST_CASE_METHOD(FetchSFTPTestsFixture, "FetchSFTP fetch non-readable file", "[F
   plan->setProperty(fetch_sftp, "Remote File", "nifi_test/tstFile.ext");
 
   createFile("nifi_test/tstFile.ext", "Test content 1");
-  REQUIRE(0 == chmod((src_dir + "/vfs/nifi_test/tstFile.ext").c_str(), 0000));
+  std::filesystem::permissions(src_dir / "vfs" / "nifi_test" / "tstFile.ext", static_cast<std::filesystem::perms>(0000));
 
   testController.runSession(plan, true);
 
@@ -311,7 +272,7 @@ TEST_CASE_METHOD(FetchSFTPTestsFixture, "FetchSFTP Completion Strategy Delete Fi
 
   createFile("nifi_test/tstFile.ext", "Test content 1");
   /* By making the parent directory non-writable we make it impossible do delete the source file */
-  REQUIRE(0 == chmod((src_dir + "/vfs/nifi_test").c_str(), 0500));
+  std::filesystem::permissions(src_dir / "vfs" / "nifi_test", static_cast<std::filesystem::perms>(0500));
 
   testController.runSession(plan, true);
 
@@ -327,7 +288,7 @@ TEST_CASE_METHOD(FetchSFTPTestsFixture, "FetchSFTP Completion Strategy Delete Fi
   REQUIRE(LogTestController::getInstance().contains("key:sftp.remote.port value:" + std::to_string(sftp_server->getPort())));
   REQUIRE(LogTestController::getInstance().contains("key:path value:nifi_test/"));
   REQUIRE(LogTestController::getInstance().contains("key:filename value:tstFile.ext"));
-  REQUIRE(0 == chmod((src_dir + "/vfs/nifi_test").c_str(), 0755));
+  std::filesystem::permissions(src_dir / "vfs" / "nifi_test", static_cast<std::filesystem::perms>(0755));
 }
 #endif
 
@@ -384,8 +345,7 @@ TEST_CASE_METHOD(FetchSFTPTestsFixture, "FetchSFTP expression language test", "[
   plan->setProperty(update_attribute, "attr_Port", std::to_string(sftp_server->getPort()), true /*dynamic*/);
   plan->setProperty(update_attribute, "attr_Username", "nifiuser", true /*dynamic*/);
   plan->setProperty(update_attribute, "attr_Password", "nifipassword", true /*dynamic*/);
-  plan->setProperty(update_attribute, "attr_Private Key Path",
-    utils::file::concat_path(get_sftp_test_dir(), "resources/id_rsa"), true /*dynamic*/);
+  plan->setProperty(update_attribute, "attr_Private Key Path", (get_sftp_test_dir() / "resources" / "id_rsa").generic_string(), true /*dynamic*/);
   plan->setProperty(update_attribute, "attr_Private Key Passphrase", "privatekeypassword", true /*dynamic*/);
   plan->setProperty(update_attribute, "attr_Remote File", "nifi_test/tstFile.ext", true /*dynamic*/);
   plan->setProperty(update_attribute, "attr_Move Destination Directory", "nifi_done/", true /*dynamic*/);
diff --git a/extensions/sftp/tests/ListSFTPTests.cpp b/extensions/sftp/tests/ListSFTPTests.cpp
index 0ddabf900..f95e5e494 100644
--- a/extensions/sftp/tests/ListSFTPTests.cpp
+++ b/extensions/sftp/tests/ListSFTPTests.cpp
@@ -29,7 +29,6 @@
 #include <type_traits>
 #include <vector>
 #include <iostream>
-#include <sstream>
 #include <algorithm>
 #include <functional>
 #include <iterator>
@@ -88,7 +87,7 @@ class ListSFTPTestsFixture {
   }
 
   void createPlan(utils::Identifier* list_sftp_uuid = nullptr, const std::shared_ptr<minifi::Configure>& configuration = nullptr) {
-    const std::string state_dir = plan == nullptr ? testController.createTempDirectory() : plan->getStateDir();
+    const auto state_dir = plan == nullptr ? testController.createTempDirectory() : plan->getStateDir();
 
     log_attribute.reset();
     list_sftp.reset();
@@ -135,20 +134,11 @@ class ListSFTPTestsFixture {
   }
 
   // Create source file
-  void createFile(const std::string& relative_path, const std::string& content, std::optional<std::filesystem::file_time_type> modification_time) {
+  void createFile(const std::filesystem::path& relative_path, const std::string& content, std::optional<std::filesystem::file_time_type> modification_time) {
     std::fstream file;
-    std::stringstream ss;
-    ss << src_dir << "/vfs/" << relative_path;
-    auto full_path = ss.str();
-    std::deque<std::string> parent_dirs;
-    std::string parent_dir = full_path;
-    while (!(parent_dir = utils::file::FileUtils::get_parent_path(parent_dir)).empty()) {
-      parent_dirs.push_front(parent_dir);
-    }
-    for (const auto& dir : parent_dirs) {
-      utils::file::FileUtils::create_dir(dir);
-    }
-    file.open(ss.str(), std::ios::out);
+    std::filesystem::path full_path = src_dir / "vfs" / relative_path;
+    std::filesystem::create_directories(full_path.parent_path());
+    file.open(full_path, std::ios::out);
     file << content;
     file.close();
     if (modification_time.has_value()) {
@@ -156,13 +146,13 @@ class ListSFTPTestsFixture {
     }
   }
 
-  void createFileWithModificationTimeDiff(const std::string& relative_path, const std::string& content, std::chrono::seconds modification_timediff = -5min) {
+  void createFileWithModificationTimeDiff(const std::filesystem::path& relative_path, const std::string& content, std::chrono::seconds modification_timediff = -5min) {
     return createFile(relative_path, content, std::chrono::file_clock::now() + modification_timediff);
   }
 
  protected:
   TestController testController;
-  std::string src_dir = testController.createTempDirectory();
+  std::filesystem::path src_dir = testController.createTempDirectory();
   std::shared_ptr<TestPlan> plan;
   std::unique_ptr<SFTPTestServer> sftp_server;
   std::shared_ptr<core::Processor> list_sftp;
@@ -187,7 +177,7 @@ TEST_CASE_METHOD(ListSFTPTestsFixture, "ListSFTP list one file", "[ListSFTP][bas
 
 TEST_CASE_METHOD(ListSFTPTestsFixture, "ListSFTP public key authentication", "[ListSFTP][basic]") {
   plan->setProperty(list_sftp, "Remote File", "nifi_test/tstFile.ext");
-  plan->setProperty(list_sftp, "Private Key Path", utils::file::FileUtils::concat_path(get_sftp_test_dir(), "resources/id_rsa"));
+  plan->setProperty(list_sftp, "Private Key Path", get_sftp_test_dir() / "resources" / "id_rsa");
   plan->setProperty(list_sftp, "Private Key Passphrase", "privatekeypassword");
 
   createFileWithModificationTimeDiff("nifi_test/tstFile.ext", "Test content 1");
@@ -217,14 +207,14 @@ TEST_CASE_METHOD(ListSFTPTestsFixture, "ListSFTP list non-readable dir", "[ListS
     return;
   }
   createFileWithModificationTimeDiff("nifi_test/tstFile.ext", "Test content 1");
-  REQUIRE(0 == chmod((src_dir + "/vfs/nifi_test").c_str(), 0000));
+  std::filesystem::permissions(src_dir / "vfs" / "nifi_test", static_cast<std::filesystem::perms>(0000));
 
   testController.runSession(plan, true);
 
   REQUIRE(false == LogTestController::getInstance().contains("from ListSFTP to relationship success"));
   REQUIRE(LogTestController::getInstance().contains("Failed to open remote directory \"nifi_test\", error: LIBSSH2_FX_PERMISSION_DENIED"));
   REQUIRE(LogTestController::getInstance().contains("There are no files to list. Yielding."));
-  REQUIRE(0 == chmod((src_dir + "/vfs/nifi_test").c_str(), 0755));
+  std::filesystem::permissions(src_dir / "vfs" / "nifi_test", static_cast<std::filesystem::perms>(0755));
 }
 #endif
 
@@ -233,7 +223,7 @@ TEST_CASE_METHOD(ListSFTPTestsFixture, "ListSFTP list one file writes attributes
 
   testController.runSession(plan, true);
 
-  auto file = src_dir + "/vfs/nifi_test/tstFile.ext";
+  auto file = src_dir / "vfs" / "nifi_test" / "tstFile.ext";
   auto mtime_str = utils::timeutils::getDateTimeStr(std::chrono::time_point_cast<std::chrono::seconds>(utils::file::to_sys(utils::file::last_write_time(file).value())));
   uint64_t uid = 0;
   uint64_t gid = 0;
@@ -363,9 +353,7 @@ TEST_CASE_METHOD(ListSFTPTestsFixture, "ListSFTP Path Filter Regex", "[ListSFTP]
 #ifndef WIN32
 TEST_CASE_METHOD(ListSFTPTestsFixture, "ListSFTP Follow symlink false file symlink", "[ListSFTP][follow-symlink]") {
   createFileWithModificationTimeDiff("nifi_test/file1.ext", "Test content 1");
-  auto file1 = src_dir + "/vfs/nifi_test/file1.ext";
-  auto file2 = src_dir + "/vfs/nifi_test/file2.ext";
-  REQUIRE(0 == symlink(file1.c_str(), file2.c_str()));
+  std::filesystem::create_symlink(src_dir / "vfs" / "nifi_test" / "file1.ext", src_dir / "vfs" / "nifi_test" / "file2.ext");
 
   testController.runSession(plan, true);
 
@@ -379,9 +367,7 @@ TEST_CASE_METHOD(ListSFTPTestsFixture, "ListSFTP Follow symlink true file symlin
   plan->setProperty(list_sftp, "Follow symlink", "true");
 
   createFileWithModificationTimeDiff("nifi_test/file1.ext", "Test content 1");
-  auto file1 = src_dir + "/vfs/nifi_test/file1.ext";
-  auto file2 = src_dir + "/vfs/nifi_test/file2.ext";
-  REQUIRE(0 == symlink(file1.c_str(), file2.c_str()));
+  std::filesystem::create_symlink(src_dir / "vfs" / "nifi_test" / "file1.ext", src_dir / "vfs" / "nifi_test" / "file2.ext");
 
   testController.runSession(plan, true);
 
@@ -395,9 +381,7 @@ TEST_CASE_METHOD(ListSFTPTestsFixture, "ListSFTP Follow symlink false directory
   plan->setProperty(list_sftp, "Search Recursively", "true");
 
   createFileWithModificationTimeDiff("nifi_test/dir1/file1.ext", "Test content 1");
-  auto dir1 = src_dir + "/vfs/nifi_test/dir1";
-  auto dir2 = src_dir + "/vfs/nifi_test/dir2";
-  REQUIRE(0 == symlink(dir1.c_str(), dir2.c_str()));
+  std::filesystem::create_directory_symlink(src_dir / "vfs" / "nifi_test" / "dir1", src_dir / "vfs" / "nifi_test" / "dir2");
 
   testController.runSession(plan, true);
 
@@ -412,9 +396,7 @@ TEST_CASE_METHOD(ListSFTPTestsFixture, "ListSFTP Follow symlink true directory s
   plan->setProperty(list_sftp, "Follow symlink", "true");
 
   createFileWithModificationTimeDiff("nifi_test/dir1/file1.ext", "Test content 1");
-  auto dir1 = src_dir + "/vfs/nifi_test/dir1";
-  auto dir2 = src_dir + "/vfs/nifi_test/dir2";
-  REQUIRE(0 == symlink(dir1.c_str(), dir2.c_str()));
+  std::filesystem::create_directory_symlink(src_dir / "vfs" / "nifi_test" / "dir1", src_dir / "vfs" / "nifi_test" / "dir2");
 
   testController.runSession(plan, true);
 
@@ -491,8 +473,7 @@ TEST_CASE_METHOD(ListSFTPTestsFixture, "ListSFTP Tracking Timestamps one file an
 
   createFileWithModificationTimeDiff("nifi_test/file1.ext", "Test content 1");
 
-  auto file = src_dir + "/vfs/nifi_test/file1.ext";
-  auto mtime = utils::file::last_write_time(file).value();
+  auto mtime = utils::file::last_write_time(src_dir / "vfs" / "nifi_test" / "file1.ext").value();
   testController.runSession(plan, true);
 
   REQUIRE(LogTestController::getInstance().contains("from ListSFTP to relationship success"));
@@ -518,7 +499,7 @@ TEST_CASE_METHOD(ListSFTPTestsFixture, "ListSFTP Tracking Timestamps one file ti
 
   createFileWithModificationTimeDiff("nifi_test/file1.ext", "Test content 1");
 
-  auto file = src_dir + "/vfs/nifi_test/file1.ext";
+  auto file = src_dir / "vfs" / "nifi_test" / "file1.ext";
   auto mtime = utils::file::last_write_time(file).value();
   testController.runSession(plan, true);
 
@@ -754,8 +735,7 @@ TEST_CASE_METHOD(ListSFTPTestsFixture, "ListSFTP Tracking Entities one file anot
 
   createFileWithModificationTimeDiff("nifi_test/file1.ext", "Test content 1");
 
-  auto file = src_dir + "/vfs/nifi_test/file1.ext";
-  auto mtime = utils::file::last_write_time(file).value();
+  auto mtime = utils::file::last_write_time(src_dir / "vfs" / "nifi_test" / "file1.ext").value();
   testController.runSession(plan, true);
 
   REQUIRE(LogTestController::getInstance().contains("from ListSFTP to relationship success"));
@@ -779,7 +759,7 @@ TEST_CASE_METHOD(ListSFTPTestsFixture, "ListSFTP Tracking Entities one file time
 
   createFileWithModificationTimeDiff("nifi_test/file1.ext", "Test content 1");
 
-  auto file = src_dir + "/vfs/nifi_test/file1.ext";
+  auto file = src_dir / "vfs" / "nifi_test" / "file1.ext";
   auto mtime = utils::file::last_write_time(file).value();
 
   testController.runSession(plan, true);
@@ -812,8 +792,7 @@ TEST_CASE_METHOD(ListSFTPTestsFixture, "ListSFTP Tracking Entities one file size
 
   createFileWithModificationTimeDiff("nifi_test/file1.ext", "Test content 1");
 
-  auto file = src_dir + "/vfs/nifi_test/file1.ext";
-  auto mtime = utils::file::last_write_time(file).value();
+  auto mtime = utils::file::last_write_time(src_dir / "vfs" / "nifi_test" / "file1.ext").value();
 
   testController.runSession(plan, true);
 
diff --git a/extensions/sftp/tests/ListThenFetchSFTPTests.cpp b/extensions/sftp/tests/ListThenFetchSFTPTests.cpp
index b60f9a1fe..b721bced7 100644
--- a/extensions/sftp/tests/ListThenFetchSFTPTests.cpp
+++ b/extensions/sftp/tests/ListThenFetchSFTPTests.cpp
@@ -29,7 +29,6 @@
 #include <type_traits>
 #include <vector>
 #include <iostream>
-#include <sstream>
 #include <algorithm>
 #include <functional>
 #include <iterator>
@@ -121,7 +120,7 @@ class ListThenFetchSFTPTestsFixture {
     plan->setProperty(list_sftp, "Minimum File Size", "0 B");
     plan->setProperty(list_sftp, "Target System Timestamp Precision", "Seconds");
     plan->setProperty(list_sftp, "Remote Path", "nifi_test/");
-    plan->setProperty(list_sftp, "State File", src_dir + "/state");
+    plan->setProperty(list_sftp, "State File", (src_dir / "state").string());
 
     // Configure FetchSFTP processor
     plan->setProperty(fetch_sftp, "Hostname", "localhost");
@@ -140,7 +139,7 @@ class ListThenFetchSFTPTestsFixture {
     plan->setProperty(log_attribute, "FlowFiles To Log", "0");
 
     // Configure PutFile processor
-    plan->setProperty(put_file, "Directory", dst_dir + "/${path}");
+    plan->setProperty(put_file, "Directory", (dst_dir / "${path}").string());
     plan->setProperty(put_file, "Conflict Resolution Strategy", minifi::processors::PutFile::CONFLICT_RESOLUTION_STRATEGY_FAIL);
     plan->setProperty(put_file, "Create Missing Directories", "true");
   }
@@ -152,24 +151,13 @@ class ListThenFetchSFTPTestsFixture {
   // Create source file
   void createFile(const std::string& relative_path, const std::string& content, std::optional<std::filesystem::file_time_type> modification_time) {
     std::fstream file;
-    std::stringstream ss;
-    ss << src_dir << "/vfs/" << relative_path;
-    auto full_path = ss.str();
-    std::deque<std::string> parent_dirs;
-    std::string parent_dir = full_path;
-    while (!(parent_dir = utils::file::get_parent_path(parent_dir)).empty()) {
-      parent_dirs.push_front(parent_dir);
-    }
-    for (const auto& dir : parent_dirs) {
-      utils::file::create_dir(dir);
-    }
-    file.open(ss.str(), std::ios::out);
+    std::filesystem::path full_path = src_dir / "vfs" / relative_path;
+    std::filesystem::create_directories(full_path.parent_path());
+    file.open(full_path, std::ios::out);
     file << content;
     file.close();
     if (modification_time.has_value()) {
-      std::error_code ec;
-      utils::file::set_last_write_time(full_path, modification_time.value());
-      REQUIRE(ec.value() == 0);
+      REQUIRE(utils::file::set_last_write_time(full_path, modification_time.value()));
     }
   }
 
@@ -182,48 +170,26 @@ class ListThenFetchSFTPTestsFixture {
     IN_SOURCE
   };
 
-  void testFile(TestWhere where, const std::string& relative_path, const std::string& expected_content) {
-    std::stringstream resultFile;
-    if (where == IN_DESTINATION) {
-      resultFile << dst_dir << "/" << relative_path;
-    } else {
-      resultFile << src_dir << "/vfs/" << relative_path;
-#ifndef WIN32
-      /* Workaround for mina-sshd setting the read file's permissions to 0000 */
-      REQUIRE(0 == chmod(resultFile.str().c_str(), 0644));
-#endif
-    }
-    std::ifstream file(resultFile.str());
-    REQUIRE(true == file.good());
+  void testFile(TestWhere where, const std::filesystem::path& relative_path, std::string_view expected_content) {
+    std::filesystem::path expected_path = where == IN_DESTINATION ? dst_dir / relative_path : src_dir / "vfs" / relative_path;
+    REQUIRE(std::filesystem::exists(expected_path));
+    std::filesystem::permissions(expected_path, static_cast<std::filesystem::perms>(0644));;
+
+    std::ifstream file(expected_path);
+    REQUIRE(file.good());
     std::stringstream content;
     std::vector<char> buffer(1024U);
     while (file) {
       file.read(buffer.data(), buffer.size());
       content << std::string(buffer.data(), file.gcount());
     }
-    REQUIRE(expected_content == content.str());
-  }
-
-  void testFileNotExists(TestWhere where, const std::string& relative_path) {
-    std::stringstream resultFile;
-    if (where == IN_DESTINATION) {
-      resultFile << dst_dir << "/" << relative_path;
-    } else {
-      resultFile << src_dir << "/vfs/" << relative_path;
-#ifndef WIN32
-      /* Workaround for mina-sshd setting the read file's permissions to 0000 */
-      REQUIRE(-1 == chmod(resultFile.str().c_str(), 0644));
-#endif
-    }
-    std::ifstream file(resultFile.str());
-    REQUIRE(false == file.is_open());
-    REQUIRE(false == file.good());
+    CHECK(expected_content == content.str());
   }
 
  protected:
   TestController testController;
-  std::string src_dir = testController.createTempDirectory();
-  std::string dst_dir = testController.createTempDirectory();
+  std::filesystem::path src_dir = testController.createTempDirectory();
+  std::filesystem::path dst_dir = testController.createTempDirectory();
   std::shared_ptr<TestPlan> plan = testController.createPlan();
   std::unique_ptr<SFTPTestServer> sftp_server;
   std::shared_ptr<core::Processor> list_sftp;
diff --git a/extensions/sftp/tests/PutSFTPTests.cpp b/extensions/sftp/tests/PutSFTPTests.cpp
index 4ef70b0a8..8ce63f386 100644
--- a/extensions/sftp/tests/PutSFTPTests.cpp
+++ b/extensions/sftp/tests/PutSFTPTests.cpp
@@ -18,7 +18,6 @@
 
 #include <sys/stat.h>
 #undef NDEBUG
-#include <cassert>
 #include <cstring>
 #include <utility>
 #include <chrono>
@@ -138,9 +137,8 @@ class PutSFTPTestsFixture {
 
   // Test target file
   void testFile(const std::string& relative_path, const std::string& expected_content) {
-    std::stringstream resultFile;
-    resultFile << dst_dir << "/vfs/" << relative_path;
-    std::ifstream file(resultFile.str());
+    auto result_file = dst_dir / "vfs" / relative_path;
+    std::ifstream file(result_file);
     REQUIRE(true == file.good());
     std::stringstream content;
     std::vector<char> buffer(1024U);
@@ -152,64 +150,58 @@ class PutSFTPTestsFixture {
   }
 
   void testFileNotExists(const std::string& relative_path) {
-    std::stringstream resultFile;
-    resultFile << dst_dir << "/vfs/" << relative_path;
-    std::ifstream file(resultFile.str());
+    auto result_file = dst_dir / "vfs" / relative_path;
+    std::ifstream file(result_file);
     REQUIRE(false == file.is_open());
     REQUIRE(false == file.good());
   }
 
   void testModificationTime(const std::string& relative_path, std::filesystem::file_time_type mtime) {
-    std::stringstream resultFile;
-    resultFile << dst_dir << "/vfs/" << relative_path;
-    REQUIRE(mtime == utils::file::last_write_time(resultFile.str()).value());
+    auto result_file = dst_dir / "vfs" / relative_path;
+    REQUIRE(mtime == utils::file::last_write_time(result_file).value());
   }
 
   void testPermissions(const std::string& relative_path, uint32_t expected_permissions) {
-    std::stringstream resultFile;
-    resultFile << dst_dir << "/vfs/" << relative_path;
+    auto result_file = dst_dir / "vfs" / relative_path;
     uint32_t permissions = 0U;
-    REQUIRE(true == utils::file::get_permissions(resultFile.str(), permissions));
+    REQUIRE(true == utils::file::get_permissions(result_file, permissions));
     REQUIRE(expected_permissions == permissions);
   }
 
   void testOwner(const std::string& relative_path, uint64_t expected_uid) {
-    std::stringstream resultFile;
-    resultFile << dst_dir << "/vfs/" << relative_path;
+    auto result_file = dst_dir / "vfs" / relative_path;
+
     uint64_t uid = 0U;
     uint64_t gid = 0U;
-    REQUIRE(true == utils::file::get_uid_gid(resultFile.str(), uid, gid));
+    REQUIRE(true == utils::file::get_uid_gid(result_file, uid, gid));
     REQUIRE(expected_uid == uid);
   }
 
   void testGroup(const std::string& relative_path, uint64_t expected_gid) {
-    std::stringstream resultFile;
-    resultFile << dst_dir << "/vfs/" << relative_path;
+    auto result_file = dst_dir / "vfs" / relative_path;
+
     uint64_t uid = 0U;
     uint64_t gid = 0U;
-    REQUIRE(true == utils::file::get_uid_gid(resultFile.str(), uid, gid));
+    REQUIRE(true == utils::file::get_uid_gid(result_file, uid, gid));
     REQUIRE(expected_gid == gid);
   }
 
-  size_t directoryContentCount(const std::string& dir) {
-    size_t count = 0U;
-    utils::file::list_dir(dir, [&count](const std::string&, const std::string&) {
-      count++;
-      return true;
-    }, testController.getLogger());
-    return count;
-  }
-
  protected:
   TestController testController;
-  std::string src_dir = testController.createTempDirectory();
-  std::string dst_dir = testController.createTempDirectory();
+  std::filesystem::path src_dir = testController.createTempDirectory();
+  std::filesystem::path dst_dir = testController.createTempDirectory();
   std::shared_ptr<TestPlan> plan = testController.createPlan();
   std::unique_ptr<SFTPTestServer> sftp_server;
   std::shared_ptr<core::Processor> get_file;
   std::shared_ptr<core::Processor> put;
 };
 
+namespace {
+std::size_t directoryContentCount(const std::filesystem::path& dir) {
+  return (std::size_t)std::distance(std::filesystem::directory_iterator{dir}, std::filesystem::directory_iterator{});
+}
+}  // namespace
+
 TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP put one file", "[PutSFTP][basic]") {
   createFile(src_dir, "tstFile.ext", "tempFile");
 
@@ -244,7 +236,7 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP bad password", "[PutSFTP][authent
 }
 
 TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP public key authentication success", "[PutSFTP][authentication]") {
-  plan->setProperty(put, "Private Key Path", utils::file::concat_path(get_sftp_test_dir(), "resources/id_rsa"));
+  plan->setProperty(put, "Private Key Path", get_sftp_test_dir() / "resources" / "id_rsa");
   plan->setProperty(put, "Private Key Passphrase", "privatekeypassword");
 
   createFile(src_dir, "tstFile.ext", "tempFile");
@@ -257,7 +249,7 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP public key authentication success
 
 TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP public key authentication bad passphrase", "[PutSFTP][authentication]") {
   plan->setProperty(put, "Password", "");
-  plan->setProperty(put, "Private Key Path", utils::file::concat_path(get_sftp_test_dir(), "resources/id_rsa"));
+  plan->setProperty(put, "Private Key Path", get_sftp_test_dir() / "resources" / "id_rsa");
   plan->setProperty(put, "Private Key Passphrase", "badpassword");
 
   createFile(src_dir, "tstFile.ext", "tempFile");
@@ -273,7 +265,7 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP public key authentication bad pas
 }
 
 TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP public key authentication bad passphrase fallback to password", "[PutSFTP][authentication]") {
-  plan->setProperty(put, "Private Key Path", utils::file::concat_path(get_sftp_test_dir(), "resources/id_rsa"));
+  plan->setProperty(put, "Private Key Path", get_sftp_test_dir() / "resources" / "id_rsa");
   plan->setProperty(put, "Private Key Passphrase", "badpassword");
 
   createFile(src_dir, "tstFile.ext", "tempFile");
@@ -286,7 +278,7 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP public key authentication bad pas
 }
 
 TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP host key checking success", "[PutSFTP][hostkey]") {
-  plan->setProperty(put, "Host Key File", utils::file::concat_path(get_sftp_test_dir(), "resources/known_hosts"));
+  plan->setProperty(put, "Host Key File", get_sftp_test_dir() / "resources" / "known_hosts");
   plan->setProperty(put, "Strict Host Key Checking", "true");
 
   createFile(src_dir, "tstFile.ext", "tempFile");
@@ -300,7 +292,7 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP host key checking success", "[Put
 TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP host key checking missing strict", "[PutSFTP][hostkey]") {
   plan->setProperty(put, "Hostname", "127.0.0.1");
 
-  plan->setProperty(put, "Host Key File", utils::file::concat_path(get_sftp_test_dir(), "resources/known_hosts"));
+  plan->setProperty(put, "Host Key File", get_sftp_test_dir() / "resources" / "known_hosts");
   plan->setProperty(put, "Strict Host Key Checking", "true");
 
   createFile(src_dir, "tstFile.ext", "tempFile");
@@ -318,7 +310,7 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP host key checking missing strict"
 TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP host key checking missing non-strict", "[PutSFTP][hostkey]") {
   plan->setProperty(put, "Hostname", "127.0.0.1");
 
-  plan->setProperty(put, "Host Key File", utils::file::concat_path(get_sftp_test_dir(), "resources/known_hosts"));
+  plan->setProperty(put, "Host Key File", get_sftp_test_dir() / "resources" / "known_hosts");
   plan->setProperty(put, "Strict Host Key Checking", "false");
 
   createFile(src_dir, "tstFile.ext", "tempFile");
@@ -330,7 +322,7 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP host key checking missing non-str
 }
 
 TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP host key checking mismatch strict", "[PutSFTP][hostkey]") {
-  plan->setProperty(put, "Host Key File", utils::file::concat_path(get_sftp_test_dir(), "resources/known_hosts_mismatch"));
+  plan->setProperty(put, "Host Key File", get_sftp_test_dir() / "resources" / "known_hosts_mismatch");
   plan->setProperty(put, "Strict Host Key Checking", "true");
 
   createFile(src_dir, "tstFile.ext", "tempFile");
@@ -362,8 +354,8 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP conflict resolution rename", "[Pu
   plan->setProperty(put, "Conflict Resolution", minifi::processors::PutSFTP::CONFLICT_RESOLUTION_RENAME);
 
   createFile(src_dir, "tstFile1.ext", "content 1");
-  REQUIRE(0 == utils::file::create_dir(utils::file::concat_path(dst_dir, "vfs/nifi_test")));
-  createFile(utils::file::concat_path(dst_dir, "vfs"), "nifi_test/tstFile1.ext", "content 2");
+  REQUIRE(0 == utils::file::create_dir(dst_dir / "vfs" / "nifi_test"));
+  createFile(dst_dir / "vfs", "nifi_test/tstFile1.ext", "content 2");
 
   testController.runSession(plan, true);
 
@@ -376,8 +368,8 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP conflict resolution reject", "[Pu
   plan->setProperty(put, "Conflict Resolution", minifi::processors::PutSFTP::CONFLICT_RESOLUTION_REJECT);
 
   createFile(src_dir, "tstFile1.ext", "content 1");
-  REQUIRE(0 == utils::file::create_dir(utils::file::FileUtils::concat_path(dst_dir, "vfs/nifi_test")));
-  createFile(utils::file::FileUtils::concat_path(dst_dir, "vfs"), "nifi_test/tstFile1.ext", "content 2");
+  REQUIRE(0 == utils::file::create_dir(dst_dir / "vfs" / "nifi_test"));
+  createFile(dst_dir / "vfs", "nifi_test/tstFile1.ext", "content 2");
 
   testController.runSession(plan, true);
 
@@ -389,8 +381,8 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP conflict resolution fail", "[PutS
   plan->setProperty(put, "Conflict Resolution", minifi::processors::PutSFTP::CONFLICT_RESOLUTION_FAIL);
 
   createFile(src_dir, "tstFile1.ext", "content 1");
-  REQUIRE(0 == utils::file::FileUtils::create_dir(utils::file::FileUtils::concat_path(dst_dir, "vfs/nifi_test")));
-  createFile(utils::file::FileUtils::concat_path(dst_dir, "vfs"), "nifi_test/tstFile1.ext", "content 2");
+  REQUIRE(0 == utils::file::FileUtils::create_dir(dst_dir / "vfs" / "nifi_test"));
+  createFile(dst_dir / "vfs", "nifi_test/tstFile1.ext", "content 2");
 
   testController.runSession(plan, true);
 
@@ -402,8 +394,8 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP conflict resolution ignore", "[Pu
   plan->setProperty(put, "Conflict Resolution", minifi::processors::PutSFTP::CONFLICT_RESOLUTION_IGNORE);
 
   createFile(src_dir, "tstFile1.ext", "content 1");
-  REQUIRE(0 == utils::file::FileUtils::create_dir(utils::file::FileUtils::concat_path(dst_dir, "vfs/nifi_test")));
-  createFile(utils::file::FileUtils::concat_path(dst_dir, "vfs"), "nifi_test/tstFile1.ext", "content 2");
+  REQUIRE(0 == utils::file::FileUtils::create_dir(dst_dir / "vfs" / "nifi_test"));
+  createFile(dst_dir / "vfs", "nifi_test/tstFile1.ext", "content 2");
 
   testController.runSession(plan, true);
 
@@ -416,8 +408,8 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP conflict resolution replace", "[P
   plan->setProperty(put, "Conflict Resolution", minifi::processors::PutSFTP::CONFLICT_RESOLUTION_REPLACE);
 
   createFile(src_dir, "tstFile1.ext", "content 1");
-  REQUIRE(0 == utils::file::FileUtils::create_dir(utils::file::FileUtils::concat_path(dst_dir, "vfs/nifi_test")));
-  createFile(utils::file::FileUtils::concat_path(dst_dir, "vfs"), "nifi_test/tstFile1.ext", "content 2");
+  REQUIRE(0 == utils::file::FileUtils::create_dir(dst_dir / "vfs" / "nifi_test"));
+  createFile(dst_dir / "vfs", "nifi_test/tstFile1.ext", "content 2");
 
   testController.runSession(plan, true);
 
@@ -429,8 +421,8 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP conflict resolution none", "[PutS
   plan->setProperty(put, "Conflict Resolution", minifi::processors::PutSFTP::CONFLICT_RESOLUTION_NONE);
 
   createFile(src_dir, "tstFile1.ext", "content 1");
-  REQUIRE(0 == utils::file::FileUtils::create_dir(utils::file::FileUtils::concat_path(dst_dir, "vfs/nifi_test")));
-  createFile(utils::file::FileUtils::concat_path(dst_dir, "vfs"), "nifi_test/tstFile1.ext", "content 2");
+  REQUIRE(0 == utils::file::FileUtils::create_dir(dst_dir / "vfs" / "nifi_test"));
+  createFile(dst_dir / "vfs", "nifi_test/tstFile1.ext", "content 2");
 
   testController.runSession(plan, true);
 
@@ -461,8 +453,8 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP conflict resolution with director
   }
 
   createFile(src_dir, "tstFile1.ext", "content 1");
-  REQUIRE(0 == utils::file::FileUtils::create_dir(utils::file::FileUtils::concat_path(dst_dir, "vfs/nifi_test")));
-  REQUIRE(0 == utils::file::FileUtils::create_dir(utils::file::FileUtils::concat_path(dst_dir, "vfs/nifi_test/tstFile1.ext")));
+  REQUIRE(0 == utils::file::FileUtils::create_dir(dst_dir / "vfs" / "nifi_test"));
+  REQUIRE(0 == utils::file::FileUtils::create_dir(dst_dir / "vfs" / "nifi_test" / "tstFile1.ext"));
 
   testController.runSession(plan, true);
 
@@ -571,8 +563,8 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP test dot rename", "[PutSFTP]") {
    * We create the would-be dot renamed file in the target, and because we don't overwrite temporary files,
    * if we really use a dot renamed temporary file, we should fail.
    */
-  REQUIRE(0 == utils::file::FileUtils::create_dir(utils::file::FileUtils::concat_path(dst_dir, "vfs/nifi_test")));
-  createFile(utils::file::FileUtils::concat_path(dst_dir, "vfs"), "nifi_test/.tstFile1.ext", "");
+  REQUIRE(0 == utils::file::FileUtils::create_dir(dst_dir / "vfs" / "nifi_test"));
+  createFile(dst_dir / "vfs", "nifi_test/.tstFile1.ext", "");
 
   testController.runSession(plan, true);
 
@@ -603,8 +595,8 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP test temporary filename", "[PutSF
    * We create the would-be temporary file in the target, and because we don't overwrite temporary files,
    * if we really use the temporary file, we should fail.
    */
-  REQUIRE(0 == utils::file::FileUtils::create_dir(utils::file::FileUtils::concat_path(dst_dir, "vfs/nifi_test")));
-  createFile(utils::file::FileUtils::concat_path(dst_dir, "vfs"), "nifi_test/tstFile1.ext.temp", "");
+  REQUIRE(0 == utils::file::FileUtils::create_dir(dst_dir / "vfs" / "nifi_test"));
+  createFile(dst_dir / "vfs", "nifi_test/tstFile1.ext.temp", "");
 
   testController.runSession(plan, true);
 
@@ -621,8 +613,8 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP test temporary file cleanup", "[P
   plan->setProperty(put, "Conflict Resolution", minifi::processors::PutSFTP::CONFLICT_RESOLUTION_NONE);
 
   createFile(src_dir, "tstFile1.ext", "content 1");
-  REQUIRE(0 == utils::file::FileUtils::create_dir(utils::file::FileUtils::concat_path(dst_dir, "vfs/nifi_test")));
-  createFile(utils::file::FileUtils::concat_path(dst_dir, "vfs"), "nifi_test/tstFile1.ext", "content 2");
+  REQUIRE(0 == utils::file::FileUtils::create_dir(dst_dir / "vfs" / "nifi_test"));
+  createFile(dst_dir / "vfs", "nifi_test/tstFile1.ext", "content 2");
 
   testController.runSession(plan, true);
 
@@ -763,7 +755,7 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP batching two files in one batch",
 
   testController.runSession(plan, true);
 
-  REQUIRE(2U == directoryContentCount(std::string(dst_dir) + "/vfs/nifi_test"));
+  REQUIRE(2U == directoryContentCount(dst_dir / "vfs" / "nifi_test"));
 }
 
 TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP batching two files in two batches", "[PutSFTP][batching]") {
@@ -773,14 +765,14 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP batching two files in two batches
   createFile(src_dir, "tstFile2.ext", "content 2");
 
   testController.runSession(plan, true);
-  REQUIRE(1U == directoryContentCount(std::string(dst_dir) + "/vfs/nifi_test"));
+  REQUIRE(1U == directoryContentCount(dst_dir / "vfs" / "nifi_test"));
   plan->reset();
 
   createFile(src_dir, "tstFile1.ext", "content 1");
   createFile(src_dir, "tstFile2.ext", "content 2");
 
   testController.runSession(plan, true);
-  REQUIRE(2U == directoryContentCount(std::string(dst_dir) + "/vfs/nifi_test"));
+  REQUIRE(2U == directoryContentCount(dst_dir / "vfs" / "nifi_test"));
   plan->reset();
 }
 
@@ -792,9 +784,9 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP batching does not fail even if on
   createFile(src_dir, "tstFile2.ext", "content 2");
   createFile(src_dir, "tstFile3.ext", "content 3");
 
-  REQUIRE(0 == utils::file::FileUtils::create_dir(utils::file::FileUtils::concat_path(dst_dir, "vfs/nifi_test")));
-  createFile(utils::file::FileUtils::concat_path(dst_dir, "vfs"), "nifi_test/tstFile1.ext", "content other");
-  createFile(utils::file::FileUtils::concat_path(dst_dir, "vfs"), "nifi_test/tstFile2.ext", "content other");
+  REQUIRE(0 == utils::file::FileUtils::create_dir(dst_dir / "vfs/nifi_test"));
+  createFile(dst_dir / "vfs", "nifi_test/tstFile1.ext", "content other");
+  createFile(dst_dir / "vfs", "nifi_test/tstFile2.ext", "content other");
 
   testController.runSession(plan, true);
 
@@ -846,8 +838,7 @@ TEST_CASE_METHOD(PutSFTPTestsFixture, "PutSFTP expression language test", "[PutS
   plan->setProperty(update_attribute, "attr_Port", std::to_string(sftp_server->getPort()), true /*dynamic*/);
   plan->setProperty(update_attribute, "attr_Username", "nifiuser", true /*dynamic*/);
   plan->setProperty(update_attribute, "attr_Password", "nifipassword", true /*dynamic*/);
-  plan->setProperty(update_attribute, "attr_Private Key Path",
-      utils::file::FileUtils::concat_path(get_sftp_test_dir(), "resources/id_rsa"), true /*dynamic*/);
+  plan->setProperty(update_attribute, "attr_Private Key Path", get_sftp_test_dir() / "resources/id_rsa", true /*dynamic*/);
   plan->setProperty(update_attribute, "attr_Private Key Passphrase", "privatekeypassword", true /*dynamic*/);
   plan->setProperty(update_attribute, "attr_Remote Path", "nifi_test/", true /*dynamic*/);
   plan->setProperty(update_attribute, "attr_Temporary Filename", "tempfile", true /*dynamic*/);
diff --git a/extensions/sftp/tests/tools/SFTPTestServer.cpp b/extensions/sftp/tests/tools/SFTPTestServer.cpp
index 7d9223f91..9d1f1d0f7 100644
--- a/extensions/sftp/tests/tools/SFTPTestServer.cpp
+++ b/extensions/sftp/tests/tools/SFTPTestServer.cpp
@@ -38,9 +38,9 @@
 
 namespace utils = org::apache::nifi::minifi::utils;
 
-SFTPTestServer::SFTPTestServer(std::string working_directory,
-                               const std::string& host_key_file /*= "resources/host.pem"*/,
-                               const std::string& jar_path /*= "tools/sftp-test-server/target/SFTPTestServer-1.0.0.jar"*/)
+SFTPTestServer::SFTPTestServer(std::filesystem::path working_directory,
+                               const std::filesystem::path& host_key_file /*= "resources/host.pem"*/,
+                               const std::filesystem::path& jar_path /*= "tools/sftp-test-server/target/SFTPTestServer-1.0.0.jar"*/)
     : working_directory_(std::move(working_directory)),
       started_(false),
       port_(0U)
@@ -48,8 +48,8 @@ SFTPTestServer::SFTPTestServer(std::string working_directory,
       , server_pid_(-1)
 #endif
 {
-  host_key_file_ = utils::file::FileUtils::concat_path(get_sftp_test_dir(), host_key_file);
-  jar_path_ = utils::file::FileUtils::concat_path(get_sftp_test_dir(), jar_path);
+  host_key_file_ = get_sftp_test_dir() / host_key_file;
+  jar_path_ = get_sftp_test_dir() / jar_path;
 }
 
 SFTPTestServer::~SFTPTestServer() {
@@ -67,13 +67,13 @@ bool SFTPTestServer::start() {
   throw std::runtime_error("Not implemented");
 #else
   /* Delete possible previous port.txt */
-  port_file_path_ = utils::file::FileUtils::concat_path(working_directory_, "port.txt");
+  port_file_path_ = working_directory_ / "port.txt";
   if (!port_file_path_.empty()) {
     logger_->log_debug("Deleting port file %s", port_file_path_.c_str());
     ::unlink(port_file_path_.c_str());
   }
 
-  auto server_log_file_path = utils::file::FileUtils::concat_path(working_directory_, "log.txt");
+  auto server_log_file_path = working_directory_ / "log.txt";
 
   /* fork */
   pid_t pid = fork();
@@ -82,7 +82,9 @@ bool SFTPTestServer::start() {
     std::vector<char*> args(4U);
     args[0] = strdup("/bin/sh");
     args[1] = strdup("-c");
-    args[2] = strdup(("exec java -Djava.security.egd=file:/dev/./urandom -jar " + jar_path_ + " -w " + working_directory_ + " -k " + host_key_file_ + " >" + server_log_file_path + " 2>&1").c_str());
+    args[2] = strdup(("exec java -Djava.security.egd=file:/dev/./urandom -jar " + jar_path_.string()
+        + " -w " + working_directory_.string()
+        + " -k " + host_key_file_.string() + " >" + server_log_file_path.string() + " 2>&1").c_str());
     args[3] = nullptr;
     execv("/bin/sh", args.data());
     std::cerr << "Failed to start server, errno: " << strerror(errno) << std::endl;
@@ -147,6 +149,6 @@ uint16_t SFTPTestServer::getPort() const {
   return port_;
 }
 
-std::string get_sftp_test_dir() {
-  return utils::file::FileUtils::concat_path(utils::file::FileUtils::get_executable_dir(), "sftp-test");
+std::filesystem::path get_sftp_test_dir() {
+  return utils::file::FileUtils::get_executable_dir() / "sftp-test";
 }
diff --git a/extensions/sftp/tests/tools/SFTPTestServer.h b/extensions/sftp/tests/tools/SFTPTestServer.h
index 401b89fa4..fb540e1be 100644
--- a/extensions/sftp/tests/tools/SFTPTestServer.h
+++ b/extensions/sftp/tests/tools/SFTPTestServer.h
@@ -29,13 +29,13 @@
 #include "core/logging/Logger.h"
 #include "core/logging/LoggerConfiguration.h"
 
-std::string get_sftp_test_dir();
+std::filesystem::path get_sftp_test_dir();
 
 class SFTPTestServer {
  public:
-  SFTPTestServer(std::string working_directory,
-      const std::string& host_key_file = "resources/host.pem",
-      const std::string& jar_path = "tools/sftp-test-server/target/SFTPTestServer-1.0.0.jar");
+  SFTPTestServer(std::filesystem::path working_directory,
+      const std::filesystem::path& host_key_file = "resources/host.pem",
+      const std::filesystem::path& jar_path = "tools/sftp-test-server/target/SFTPTestServer-1.0.0.jar");
   ~SFTPTestServer();
 
   bool start();
@@ -45,11 +45,11 @@ class SFTPTestServer {
  private:
   std::shared_ptr<org::apache::nifi::minifi::core::logging::Logger> logger_ = org::apache::nifi::minifi::core::logging::LoggerFactory<SFTPTestServer>::getLogger();
 
-  std::string host_key_file_;
-  std::string jar_path_;
-  std::string working_directory_;
+  std::filesystem::path host_key_file_;
+  std::filesystem::path jar_path_;
+  std::filesystem::path working_directory_;
   bool started_;
-  std::string port_file_path_;
+  std::filesystem::path port_file_path_;
   uint16_t port_;
 #ifndef WIN32
   pid_t server_pid_;
diff --git a/extensions/standard-processors/processors/ExecuteProcess.cpp b/extensions/standard-processors/processors/ExecuteProcess.cpp
index dfa32fedc..a643f9be9 100644
--- a/extensions/standard-processors/processors/ExecuteProcess.cpp
+++ b/extensions/standard-processors/processors/ExecuteProcess.cpp
@@ -79,8 +79,8 @@ void ExecuteProcess::onSchedule(core::ProcessContext* context, core::ProcessSess
   if (context->getProperty(CommandArguments.getName(), value)) {
     command_argument_ = value;
   }
-  if (context->getProperty(WorkingDir.getName(), value)) {
-    working_dir_ = value;
+  if (auto working_dir_str = context->getProperty(WorkingDir)) {
+    working_dir_ = *working_dir_str;
   }
   if (auto batch_duration = context->getProperty<core::TimePeriodValue>(BatchDuration)) {
     batch_duration_ = batch_duration->getMilliseconds();
@@ -241,7 +241,9 @@ void ExecuteProcess::onTrigger(core::ProcessContext *context, core::ProcessSessi
     yield();
     return;
   }
-  if (!utils::Environment::setCurrentWorkingDirectory(working_dir_.c_str())) {
+  std::error_code current_path_error;
+  std::filesystem::current_path(working_dir_, current_path_error);
+  if (current_path_error) {
     yield();
     return;
   }
diff --git a/extensions/standard-processors/processors/ExecuteProcess.h b/extensions/standard-processors/processors/ExecuteProcess.h
index e8e3c0d97..9cbea5b43 100644
--- a/extensions/standard-processors/processors/ExecuteProcess.h
+++ b/extensions/standard-processors/processors/ExecuteProcess.h
@@ -102,7 +102,7 @@ class ExecuteProcess : public core::Processor {
   std::shared_ptr<core::logging::Logger> logger_ = core::logging::LoggerFactory<ExecuteProcess>::getLogger();
   std::string command_;
   std::string command_argument_;
-  std::string working_dir_;
+  std::filesystem::path working_dir_;
   std::chrono::milliseconds batch_duration_  = std::chrono::milliseconds(0);
   bool redirect_error_stream_;
   std::string full_command_;
diff --git a/extensions/standard-processors/processors/FetchFile.cpp b/extensions/standard-processors/processors/FetchFile.cpp
index cf18ebeae..4fcce6ce7 100644
--- a/extensions/standard-processors/processors/FetchFile.cpp
+++ b/extensions/standard-processors/processors/FetchFile.cpp
@@ -140,15 +140,15 @@ void FetchFile::logWithLevel(LogLevelOption log_level, Args&&... args) const {
   }
 }
 
-std::string FetchFile::getMoveAbsolutePath(const std::string& file_name) const {
-  return move_destination_directory_ + utils::file::FileUtils::get_separator() + file_name;
+std::filesystem::path FetchFile::getMoveAbsolutePath(const std::filesystem::path& file_name) const {
+  return move_destination_directory_ / file_name;
 }
 
-bool FetchFile::moveDestinationConflicts(const std::string& file_name) const {
+bool FetchFile::moveDestinationConflicts(const std::filesystem::path& file_name) const {
   return utils::file::FileUtils::exists(getMoveAbsolutePath(file_name));
 }
 
-bool FetchFile::moveWouldFailWithDestinationConflict(const std::string& file_name) const {
+bool FetchFile::moveWouldFailWithDestinationConflict(const std::filesystem::path& file_name) const {
   if (completion_strategy_ != CompletionStrategyOption::MOVE_FILE || move_confict_strategy_ != MoveConflictStrategyOption::FAIL) {
     return false;
   }
@@ -156,28 +156,28 @@ bool FetchFile::moveWouldFailWithDestinationConflict(const std::string& file_nam
   return moveDestinationConflicts(file_name);
 }
 
-void FetchFile::executeMoveConflictStrategy(const std::string& file_to_fetch_path, const std::string& file_name) {
+void FetchFile::executeMoveConflictStrategy(const std::filesystem::path& file_to_fetch_path, const std::filesystem::path& file_name) {
   if (move_confict_strategy_ == MoveConflictStrategyOption::REPLACE_FILE) {
     auto moved_path = getMoveAbsolutePath(file_name);
-    logger_->log_debug("Due to conflict replacing file '%s' by the Move Completion Strategy", moved_path);
+    logger_->log_debug("Due to conflict replacing file '%s' by the Move Completion Strategy", moved_path.string());
     std::filesystem::rename(file_to_fetch_path, moved_path);
   } else if (move_confict_strategy_ == MoveConflictStrategyOption::RENAME) {
-    auto generated_filename = utils::IdGenerator::getIdGenerator()->generate().to_string();
-    logger_->log_debug("Due to conflict file '%s' is moved with generated name '%s' by the Move Completion Strategy", file_to_fetch_path, generated_filename);
+    std::filesystem::path generated_filename{utils::IdGenerator::getIdGenerator()->generate().to_string().view()};
+    logger_->log_debug("Due to conflict file '%s' is moved with generated name '%s' by the Move Completion Strategy", file_to_fetch_path.string(), generated_filename.string());
     std::filesystem::rename(file_to_fetch_path, getMoveAbsolutePath(generated_filename));
   } else if (move_confict_strategy_ == MoveConflictStrategyOption::KEEP_EXISTING) {
-    logger_->log_debug("Due to conflict file '%s' is deleted by the Move Completion Strategy", file_to_fetch_path);
+    logger_->log_debug("Due to conflict file '%s' is deleted by the Move Completion Strategy", file_to_fetch_path.string());
     std::filesystem::remove(file_to_fetch_path);
   }
 }
 
-void FetchFile::processMoveCompletion(const std::string& file_to_fetch_path, const std::string& file_name) {
+void FetchFile::processMoveCompletion(const std::filesystem::path& file_to_fetch_path, const std::filesystem::path& file_name) {
   if (!moveDestinationConflicts(file_name)) {
     if (!utils::file::FileUtils::exists(move_destination_directory_)) {
       std::filesystem::create_directories(move_destination_directory_);
     }
     auto moved_path = getMoveAbsolutePath(file_name);
-    logger_->log_debug("'%s' is moved to '%s' by the Move Completion Strategy", file_to_fetch_path, moved_path);
+    logger_->log_debug("'%s' is moved to '%s' by the Move Completion Strategy", file_to_fetch_path.string(), moved_path.string());
     std::filesystem::rename(file_to_fetch_path, moved_path);
     return;
   }
@@ -185,12 +185,12 @@ void FetchFile::processMoveCompletion(const std::string& file_to_fetch_path, con
   executeMoveConflictStrategy(file_to_fetch_path, file_name);
 }
 
-void FetchFile::executeCompletionStrategy(const std::string& file_to_fetch_path, const std::string& file_name) {
+void FetchFile::executeCompletionStrategy(const std::filesystem::path& file_to_fetch_path, const std::filesystem::path& file_name) {
   try {
     if (completion_strategy_ == CompletionStrategyOption::MOVE_FILE) {
       processMoveCompletion(file_to_fetch_path, file_name);
     } else if (completion_strategy_ == CompletionStrategyOption::DELETE_FILE) {
-      logger_->log_debug("File '%s' is deleted by the Delete Completion Strategy", file_to_fetch_path);
+      logger_->log_debug("File '%s' is deleted by the Delete Completion Strategy", file_to_fetch_path.string());
       std::filesystem::remove(file_to_fetch_path);
     }
   } catch(const std::filesystem::filesystem_error& ex) {
@@ -208,41 +208,40 @@ void FetchFile::onTrigger(const std::shared_ptr<core::ProcessContext> &context,
   }
 
   const auto file_to_fetch_path = getFileToFetch(*context, flow_file);
-  auto file_fetch_path_str = file_to_fetch_path.string();
   if (!std::filesystem::is_regular_file(file_to_fetch_path)) {
-    logWithLevel(log_level_when_file_not_found_, "File to fetch was not found: '%s'!", file_fetch_path_str);
+    logWithLevel(log_level_when_file_not_found_, "File to fetch was not found: '%s'!", file_to_fetch_path.string());
     session->transfer(flow_file, NotFound);
     return;
   }
 
-  std::string file_path;
-  std::string file_name;
-  utils::file::getFileNameAndPath(file_fetch_path_str, file_path, file_name);
+  auto file_name = file_to_fetch_path.filename();
 
-  context->getProperty(MoveDestinationDirectory, move_destination_directory_, flow_file);
+  std::string move_destination_directory;
+  context->getProperty(MoveDestinationDirectory, move_destination_directory, flow_file);
+  move_destination_directory_ = move_destination_directory;
   if (moveWouldFailWithDestinationConflict(file_name)) {
-    logger_->log_error("Move destination (%s) conflicts with an already existing file!", move_destination_directory_);
+    logger_->log_error("Move destination (%s) conflicts with an already existing file!", move_destination_directory_.string());
     session->transfer(flow_file, Failure);
     return;
   }
 
   try {
-    utils::FileReaderCallback callback(file_fetch_path_str);
+    utils::FileReaderCallback callback(file_to_fetch_path);
     session->write(flow_file, std::move(callback));
-    logger_->log_debug("Fetching file '%s' successful!", file_fetch_path_str);
+    logger_->log_debug("Fetching file '%s' successful!", file_to_fetch_path.string());
     session->transfer(flow_file, Success);
   } catch (const utils::FileReaderCallbackIOError& io_error) {
     if (io_error.error_code == EACCES) {
-      logWithLevel(log_level_when_permission_denied_, "Read permission denied for file '%s' to be fetched!", file_fetch_path_str);
+      logWithLevel(log_level_when_permission_denied_, "Read permission denied for file '%s' to be fetched!", file_to_fetch_path.string());
       session->transfer(flow_file, PermissionDenied);
     } else {
-      logger_->log_error("Fetching file '%s' failed! %s", file_fetch_path_str, io_error.what());
+      logger_->log_error("Fetching file '%s' failed! %s", file_to_fetch_path.string(), io_error.what());
       session->transfer(flow_file, Failure);
     }
     return;
   }
 
-  executeCompletionStrategy(file_fetch_path_str, file_name);
+  executeCompletionStrategy(file_to_fetch_path, file_name);
 }
 
 REGISTER_RESOURCE(FetchFile, Processor);
diff --git a/extensions/standard-processors/processors/FetchFile.h b/extensions/standard-processors/processors/FetchFile.h
index 70112fa59..747bf0aeb 100644
--- a/extensions/standard-processors/processors/FetchFile.h
+++ b/extensions/standard-processors/processors/FetchFile.h
@@ -105,14 +105,14 @@ class FetchFile : public core::Processor {
   void logWithLevel(LogLevelOption log_level, Args&&... args) const;
 
   static std::filesystem::path getFileToFetch(core::ProcessContext& context, const std::shared_ptr<core::FlowFile>& flow_file);
-  std::string getMoveAbsolutePath(const std::string& file_name) const;
-  bool moveDestinationConflicts(const std::string& file_name) const;
-  bool moveWouldFailWithDestinationConflict(const std::string& file_name) const;
-  void executeMoveConflictStrategy(const std::string& file_to_fetch_path, const std::string& file_name);
-  void processMoveCompletion(const std::string& file_to_fetch_path, const std::string& file_name);
-  void executeCompletionStrategy(const std::string& file_to_fetch_path, const std::string& file_name);
-
-  std::string move_destination_directory_;
+  std::filesystem::path getMoveAbsolutePath(const std::filesystem::path& file_name) const;
+  bool moveDestinationConflicts(const std::filesystem::path& file_name) const;
+  bool moveWouldFailWithDestinationConflict(const std::filesystem::path& file_name) const;
+  void executeMoveConflictStrategy(const std::filesystem::path& file_to_fetch_path, const std::filesystem::path& file_name);
+  void processMoveCompletion(const std::filesystem::path& file_to_fetch_path, const std::filesystem::path& file_name);
+  void executeCompletionStrategy(const std::filesystem::path& file_to_fetch_path, const std::filesystem::path& file_name);
+
+  std::filesystem::path move_destination_directory_;
   CompletionStrategyOption completion_strategy_;
   MoveConflictStrategyOption move_confict_strategy_;
   LogLevelOption log_level_when_file_not_found_;
diff --git a/extensions/standard-processors/processors/GetFile.cpp b/extensions/standard-processors/processors/GetFile.cpp
index 10480b518..b3d0558fc 100644
--- a/extensions/standard-processors/processors/GetFile.cpp
+++ b/extensions/standard-processors/processors/GetFile.cpp
@@ -152,36 +152,33 @@ void GetFile::onTrigger(core::ProcessContext* /*context*/, core::ProcessSession*
     return;
   }
 
-  std::queue<std::string> list_of_file_names = pollListing(request_.batchSize);
+  std::queue<std::filesystem::path> list_of_file_names = pollListing(request_.batchSize);
   while (!list_of_file_names.empty()) {
-    std::string file_name = list_of_file_names.front();
+    auto file_name = list_of_file_names.front();
     list_of_file_names.pop();
     getSingleFile(*session, file_name);
   }
 }
 
-void GetFile::getSingleFile(core::ProcessSession& session, const std::string& file_name) const {
-  logger_->log_info("GetFile process %s", file_name);
+void GetFile::getSingleFile(core::ProcessSession& session, const std::filesystem::path& file_path) const {
+  logger_->log_info("GetFile process %s", file_path.string());
   auto flow_file = session.create();
   gsl_Expects(flow_file);
-  std::string path;
-  std::string name;
-  std::tie(path, name) = utils::file::split_path(file_name);
-  flow_file->setAttribute(core::SpecialFlowAttribute::FILENAME, name);
-  flow_file->setAttribute(core::SpecialFlowAttribute::PATH, path);
-  flow_file->addAttribute(core::SpecialFlowAttribute::ABSOLUTE_PATH, file_name);
+  flow_file->setAttribute(core::SpecialFlowAttribute::FILENAME, file_path.filename().string());
+  flow_file->setAttribute(core::SpecialFlowAttribute::PATH, (file_path.parent_path() / "").string());
+  flow_file->addAttribute(core::SpecialFlowAttribute::ABSOLUTE_PATH, file_path.string());
 
   try {
-    session.write(flow_file, utils::FileReaderCallback{file_name});
+    session.write(flow_file, utils::FileReaderCallback{file_path});
     session.transfer(flow_file, Success);
     if (!request_.keepSourceFile) {
-      auto remove_status = remove(file_name.c_str());
-      if (remove_status != 0) {
-        logger_->log_error("GetFile could not delete file '%s', error %d: %s", file_name, errno, strerror(errno));
+      std::error_code remove_error;
+      if (!std::filesystem::remove(file_path, remove_error)) {
+        logger_->log_error("GetFile could not delete file '%s', error: %s", file_path.string(), remove_error.message());
       }
     }
   } catch (const utils::FileReaderCallbackIOError& io_error) {
-    logger_->log_error("IO error while processing file '%s': %s", file_name, io_error.what());
+    logger_->log_error("IO error while processing file '%s': %s", file_path.string(), io_error.what());
     flow_file->setDeleted(true);
   }
 }
@@ -192,18 +189,18 @@ bool GetFile::isListingEmpty() const {
   return directory_listing_.empty();
 }
 
-void GetFile::putListing(const std::string& fileName) {
-  logger_->log_trace("Adding file to queue: %s", fileName);
+void GetFile::putListing(const std::filesystem::path& file_path) {
+  logger_->log_trace("Adding file to queue: %s", file_path.string());
 
   std::lock_guard<std::mutex> lock(directory_listing_mutex_);
 
-  directory_listing_.push(fileName);
+  directory_listing_.push(file_path);
 }
 
-std::queue<std::string> GetFile::pollListing(uint64_t batch_size) {
+std::queue<std::filesystem::path> GetFile::pollListing(uint64_t batch_size) {
   std::lock_guard<std::mutex> lock(directory_listing_mutex_);
 
-  std::queue<std::string> list;
+  std::queue<std::filesystem::path> list;
   while (!directory_listing_.empty() && (batch_size == 0 || list.size() < batch_size)) {
     list.push(directory_listing_.front());
     directory_listing_.pop();
@@ -211,18 +208,18 @@ std::queue<std::string> GetFile::pollListing(uint64_t batch_size) {
   return list;
 }
 
-bool GetFile::fileMatchesRequestCriteria(const std::string& fullName, const std::string& name, const GetFileRequest &request) {
-  logger_->log_trace("Checking file: %s", fullName);
+bool GetFile::fileMatchesRequestCriteria(const std::filesystem::path& full_name, const std::filesystem::path& name, const GetFileRequest &request) {
+  logger_->log_trace("Checking file: %s", full_name.string());
 
   std::error_code ec;
-  uint64_t file_size = std::filesystem::file_size(fullName, ec);
+  uint64_t file_size = std::filesystem::file_size(full_name, ec);
   if (ec) {
-    logger_->log_error("file_size of %s: %s", fullName, ec.message());
+    logger_->log_error("file_size of %s: %s", full_name.string(), ec.message());
     return false;
   }
-  const auto modifiedTime = std::filesystem::last_write_time(fullName, ec);
+  const auto modifiedTime = std::filesystem::last_write_time(full_name, ec);
   if (ec) {
-    logger_->log_error("last_write_time of %s: %s", fullName, ec.message());
+    logger_->log_error("last_write_time of %s: %s", full_name.string(), ec.message());
     return false;
   }
 
@@ -238,11 +235,11 @@ bool GetFile::fileMatchesRequestCriteria(const std::string& fullName, const std:
   if (request.maxAge > 0ms && fileAge > request.maxAge)
     return false;
 
-  if (request.ignoreHiddenFile && utils::file::is_hidden(fullName))
+  if (request.ignoreHiddenFile && utils::file::is_hidden(full_name))
     return false;
 
   utils::Regex rgx(request.fileFilter);
-  if (!utils::regexMatch(name, rgx)) {
+  if (!utils::regexMatch(name.string(), rgx)) {
     return false;
   }
 
@@ -253,8 +250,8 @@ bool GetFile::fileMatchesRequestCriteria(const std::string& fullName, const std:
 }
 
 void GetFile::performListing(const GetFileRequest &request) {
-  auto callback = [this, request](const std::string& dir, const std::string& filename) -> bool {
-    std::string fullpath = dir + utils::file::get_separator() + filename;
+  auto callback = [this, request](const std::filesystem::path& dir, const std::filesystem::path& filename) -> bool {
+    auto fullpath = dir / filename;
     if (fileMatchesRequestCriteria(fullpath, filename, request)) {
       putListing(fullpath);
     }
diff --git a/extensions/standard-processors/processors/GetFile.h b/extensions/standard-processors/processors/GetFile.h
index ada85962e..b320e07cd 100644
--- a/extensions/standard-processors/processors/GetFile.h
+++ b/extensions/standard-processors/processors/GetFile.h
@@ -136,13 +136,13 @@ class GetFile : public core::Processor {
 
  private:
   bool isListingEmpty() const;
-  void putListing(const std::string& fileName);
-  std::queue<std::string> pollListing(uint64_t batch_size);
-  bool fileMatchesRequestCriteria(const std::string& fullName, const std::string& name, const GetFileRequest &request);
-  void getSingleFile(core::ProcessSession& session, const std::string& file_name) const;
+  void putListing(const std::filesystem::path& file_path);
+  std::queue<std::filesystem::path> pollListing(uint64_t batch_size);
+  bool fileMatchesRequestCriteria(const std::filesystem::path& full_name, const std::filesystem::path& name, const GetFileRequest &request);
+  void getSingleFile(core::ProcessSession& session, const std::filesystem::path& file_path) const;
 
   GetFileRequest request_;
-  std::queue<std::string> directory_listing_;
+  std::queue<std::filesystem::path> directory_listing_;
   mutable std::mutex directory_listing_mutex_;
   std::atomic<std::chrono::time_point<std::chrono::system_clock>> last_listing_time_{};
   std::shared_ptr<core::logging::Logger> logger_ = core::logging::LoggerFactory<GetFile>::getLogger();
diff --git a/extensions/standard-processors/processors/ListFile.cpp b/extensions/standard-processors/processors/ListFile.cpp
index 37be37be4..f65595e1d 100644
--- a/extensions/standard-processors/processors/ListFile.cpp
+++ b/extensions/standard-processors/processors/ListFile.cpp
@@ -95,8 +95,11 @@ void ListFile::onSchedule(const std::shared_ptr<core::ProcessContext> &context,
   }
   state_manager_ = std::make_unique<minifi::utils::ListingStateManager>(state_manager);
 
-  if (!context->getProperty(InputDirectory.getName(), input_directory_) || input_directory_.empty()) {
+  std::string input_directory_str;
+  if (auto input_directory_str = context->getProperty(InputDirectory); !input_directory_str || input_directory_str->empty()) {
     throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Input Directory property missing or invalid");
+  } else {
+    input_directory_ = *input_directory_str;
   }
 
   context->getProperty(RecurseSubdirectories.getName(), recurse_subdirectories_);
@@ -131,38 +134,38 @@ void ListFile::onSchedule(const std::shared_ptr<core::ProcessContext> &context,
 
 bool ListFile::fileMatchesFilters(const ListedFile& listed_file) {
   if (ignore_hidden_files_ && utils::file::FileUtils::is_hidden(listed_file.full_file_path)) {
-    logger_->log_debug("File '%s' is hidden so it will not be listed", listed_file.full_file_path);
+    logger_->log_debug("File '%s' is hidden so it will not be listed", listed_file.full_file_path.string());
     return false;
   }
 
-  if (file_filter_ && !std::regex_match(listed_file.filename, *file_filter_)) {
-    logger_->log_debug("File '%s' does not match file filter so it will not be listed", listed_file.full_file_path);
+  if (file_filter_ && !std::regex_match(listed_file.filename.string(), *file_filter_)) {
+    logger_->log_debug("File '%s' does not match file filter so it will not be listed", listed_file.full_file_path.string());
     return false;
   }
 
-  if (path_filter_ && listed_file.relative_path != "." && !std::regex_match(listed_file.relative_path, *path_filter_)) {
-    logger_->log_debug("Relative path '%s' does not match path filter so file '%s' will not be listed", listed_file.relative_path, listed_file.full_file_path);
+  if (path_filter_ && listed_file.relative_path != "." && !std::regex_match(listed_file.relative_path.string(), *path_filter_)) {
+    logger_->log_debug("Relative path '%s' does not match path filter so file '%s' will not be listed", listed_file.relative_path.string(), listed_file.full_file_path.string());
     return false;
   }
 
   auto file_age = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - listed_file.getLastModified());
   if (minimum_file_age_ && file_age < *minimum_file_age_) {
-    logger_->log_debug("File '%s' does not meet the minimum file age requirement so it will not be listed", listed_file.full_file_path);
+    logger_->log_debug("File '%s' does not meet the minimum file age requirement so it will not be listed", listed_file.full_file_path.string());
     return false;
   }
 
   if (maximum_file_age_ && file_age > *maximum_file_age_) {
-    logger_->log_debug("File '%s' does not meet the maximum file age requirement so it will not be listed", listed_file.full_file_path);
+    logger_->log_debug("File '%s' does not meet the maximum file age requirement so it will not be listed", listed_file.full_file_path.string());
     return false;
   }
 
   if (minimum_file_size_ && listed_file.file_size < *minimum_file_size_) {
-    logger_->log_debug("File '%s' does not meet the minimum file size requirement so it will not be listed", listed_file.full_file_path);
+    logger_->log_debug("File '%s' does not meet the minimum file size requirement so it will not be listed", listed_file.full_file_path.string());
     return false;
   }
 
   if (maximum_file_size_ && *maximum_file_size_ < listed_file.file_size) {
-    logger_->log_debug("File '%s' does not meet the maximum file size requirement so it will not be listed", listed_file.full_file_path);
+    logger_->log_debug("File '%s' does not meet the maximum file size requirement so it will not be listed", listed_file.full_file_path.string());
     return false;
   }
 
@@ -171,29 +174,29 @@ bool ListFile::fileMatchesFilters(const ListedFile& listed_file) {
 
 std::shared_ptr<core::FlowFile> ListFile::createFlowFile(core::ProcessSession& session, const ListedFile& listed_file) {
   auto flow_file = session.create();
-  session.putAttribute(flow_file, core::SpecialFlowAttribute::FILENAME, listed_file.filename);
-  session.putAttribute(flow_file, core::SpecialFlowAttribute::ABSOLUTE_PATH, listed_file.absolute_path);
+  session.putAttribute(flow_file, core::SpecialFlowAttribute::FILENAME, listed_file.filename.string());
+  session.putAttribute(flow_file, core::SpecialFlowAttribute::ABSOLUTE_PATH, listed_file.absolute_path.string());
   session.putAttribute(flow_file, core::SpecialFlowAttribute::PATH, listed_file.relative_path == "." ?
-    std::string(".") + utils::file::FileUtils::get_separator() : listed_file.relative_path + utils::file::FileUtils::get_separator());
+    (std::filesystem::path(".") / "").string() : (listed_file.relative_path / "").string());
   session.putAttribute(flow_file, "file.size", std::to_string(listed_file.file_size));
   if (auto last_modified_str = utils::file::FileUtils::get_last_modified_time_formatted_string(listed_file.full_file_path, "%Y-%m-%dT%H:%M:%SZ")) {
     session.putAttribute(flow_file, "file.lastModifiedTime", *last_modified_str);
   } else {
     session.putAttribute(flow_file, "file.lastModifiedTime", "");
-    logger_->log_warn("Could not get last modification time of file '%s'", listed_file.full_file_path);
+    logger_->log_warn("Could not get last modification time of file '%s'", listed_file.full_file_path.string());
   }
 
   if (auto permission_string = utils::file::FileUtils::get_permission_string(listed_file.full_file_path)) {
     session.putAttribute(flow_file, "file.permissions", *permission_string);
   } else {
-    logger_->log_warn("Failed to get permissions of file '%s'", listed_file.full_file_path);
+    logger_->log_warn("Failed to get permissions of file '%s'", listed_file.full_file_path.string());
     session.putAttribute(flow_file, "file.permissions", "");
   }
 
   if (auto owner = utils::file::FileUtils::get_file_owner(listed_file.full_file_path)) {
     session.putAttribute(flow_file, "file.owner", *owner);
   } else {
-    logger_->log_warn("Failed to get owner of file '%s'", listed_file.full_file_path);
+    logger_->log_warn("Failed to get owner of file '%s'", listed_file.full_file_path.string());
     session.putAttribute(flow_file, "file.owner", "");
   }
 
@@ -201,7 +204,7 @@ std::shared_ptr<core::FlowFile> ListFile::createFlowFile(core::ProcessSession& s
   if (auto group = utils::file::FileUtils::get_file_group(listed_file.full_file_path)) {
     session.putAttribute(flow_file, "file.group", *group);
   } else {
-    logger_->log_warn("Failed to get group of file '%s'", listed_file.full_file_path);
+    logger_->log_warn("Failed to get group of file '%s'", listed_file.full_file_path.string());
     session.putAttribute(flow_file, "file.group", "");
   }
 #else
@@ -222,19 +225,19 @@ void ListFile::onTrigger(const std::shared_ptr<core::ProcessContext> &context, c
   auto file_list = utils::file::FileUtils::list_dir_all(input_directory_, logger_, recurse_subdirectories_);
   for (const auto& [path, filename] : file_list) {
     ListedFile listed_file;
-    listed_file.full_file_path = (std::filesystem::path(path) / filename).string();
-    listed_file.absolute_path = path + utils::file::FileUtils::get_separator();
+    listed_file.full_file_path = path / filename;
+    listed_file.absolute_path = path / "";
     if (auto relative_path = utils::file::FileUtils::get_relative_path(path, input_directory_)) {
       listed_file.relative_path = *relative_path;
     } else {
-      logger_->log_warn("Failed to get group of file '%s' to input directory '%s'", listed_file.full_file_path, input_directory_);
+      logger_->log_warn("Failed to get group of file '%s' to input directory '%s'", listed_file.full_file_path.string(), input_directory_.string());
     }
     listed_file.file_size = utils::file::FileUtils::file_size(listed_file.full_file_path);
     listed_file.filename = filename;
     if (auto last_modified_time = utils::file::FileUtils::last_write_time(listed_file.full_file_path)) {
       listed_file.last_modified_time = *last_modified_time;
     } else {
-      logger_->log_error("Could not get last modification time of file '%s'", listed_file.full_file_path);
+      logger_->log_error("Could not get last modification time of file '%s'", listed_file.full_file_path.string());
       continue;
     }
 
@@ -243,7 +246,7 @@ void ListFile::onTrigger(const std::shared_ptr<core::ProcessContext> &context, c
     }
 
     if (stored_listing_state.wasObjectListedAlready(listed_file)) {
-      logger_->log_debug("File '%s' was already listed.", listed_file.full_file_path);
+      logger_->log_debug("File '%s' was already listed.", listed_file.full_file_path.string());
       continue;
     }
 
@@ -256,7 +259,7 @@ void ListFile::onTrigger(const std::shared_ptr<core::ProcessContext> &context, c
   state_manager_->storeState(latest_listing_state);
 
   if (files_listed == 0) {
-    logger_->log_debug("No new files were found in input directory '%s' to list", input_directory_);
+    logger_->log_debug("No new files were found in input directory '%s' to list", input_directory_.string());
     context->yield();
   }
 }
diff --git a/extensions/standard-processors/processors/ListFile.h b/extensions/standard-processors/processors/ListFile.h
index 1d80bcca7..8199d5654 100644
--- a/extensions/standard-processors/processors/ListFile.h
+++ b/extensions/standard-processors/processors/ListFile.h
@@ -85,14 +85,14 @@ class ListFile : public core::Processor {
     }
 
     [[nodiscard]] std::string getKey() const override {
-      return absolute_path;
+      return absolute_path.string();
     }
 
-    std::string filename;
-    std::string absolute_path;
+    std::filesystem::path filename;
+    std::filesystem::path absolute_path;
     std::filesystem::file_time_type last_modified_time;
-    std::string relative_path;
-    std::string full_file_path;
+    std::filesystem::path relative_path;
+    std::filesystem::path full_file_path;
     uint64_t file_size = 0;
   };
 
@@ -100,7 +100,7 @@ class ListFile : public core::Processor {
   std::shared_ptr<core::FlowFile> createFlowFile(core::ProcessSession& session, const ListedFile& listed_file);
 
   std::shared_ptr<core::logging::Logger> logger_ = core::logging::LoggerFactory<ListFile>::getLogger();
-  std::string input_directory_;
+  std::filesystem::path input_directory_;
   std::unique_ptr<minifi::utils::ListingStateManager> state_manager_;
   bool recurse_subdirectories_ = true;
   std::optional<std::regex> file_filter_;
diff --git a/extensions/standard-processors/processors/PutFile.cpp b/extensions/standard-processors/processors/PutFile.cpp
index baddbb64d..2751c1aa7 100644
--- a/extensions/standard-processors/processors/PutFile.cpp
+++ b/extensions/standard-processors/processors/PutFile.cpp
@@ -109,9 +109,11 @@ void PutFile::onTrigger(core::ProcessContext *context, core::ProcessSession *ses
 
   session->remove(flowFile);
 
-  std::string directory;
+  std::filesystem::path directory;
 
-  if (!context->getProperty(Directory, directory, flowFile)) {
+  if (auto directory_str = context->getProperty(Directory, flowFile)) {
+    directory = *directory_str;
+  } else {
     logger_->log_error("Directory attribute is missing or invalid");
   }
 
@@ -123,23 +125,21 @@ void PutFile::onTrigger(core::ProcessContext *context, core::ProcessSession *ses
 
   std::string filename;
   flowFile->getAttribute(core::SpecialFlowAttribute::FILENAME, filename);
-  std::string tmpFile = tmpWritePath(filename, directory);
+  auto tmpFile = tmpWritePath(filename, directory);
 
-  logger_->log_debug("PutFile using temporary file %s", tmpFile);
+  logger_->log_debug("PutFile using temporary file %s", tmpFile.string());
 
   // Determine dest full file paths
-  std::stringstream destFileSs;
-  destFileSs << directory << utils::file::get_separator() << filename;
-  std::string destFile = destFileSs.str();
+  auto destFile = directory / filename;
 
-  logger_->log_debug("PutFile writing file %s into directory %s", filename, directory);
+  logger_->log_debug("PutFile writing file %s into directory %s", filename, directory.string());
 
   if ((max_dest_files_ != -1) && utils::file::is_directory(directory)) {
     int64_t count = 0;
 
     // Callback, called for each file entry in the listed directory
     // Return value is used to break (false) or continue (true) listing
-    auto lambda = [&count, this](const std::string&, const std::string&) -> bool {
+    auto lambda = [&count, this](const std::filesystem::path&, const std::filesystem::path&) -> bool {
       return ++count < max_dest_files_;
     };
 
@@ -147,14 +147,14 @@ void PutFile::onTrigger(core::ProcessContext *context, core::ProcessSession *ses
 
     if (count >= max_dest_files_) {
       logger_->log_warn("Routing to failure because the output directory %s has at least %u files, which exceeds the "
-                        "configured max number of files", directory, max_dest_files_);
+                        "configured max number of files", directory.string(), max_dest_files_);
       session->transfer(flowFile, Failure);
       return;
     }
   }
 
   if (utils::file::exists(destFile)) {
-    logger_->log_warn("Destination file %s exists; applying Conflict Resolution Strategy: %s", destFile, conflict_resolution_);
+    logger_->log_warn("Destination file %s exists; applying Conflict Resolution Strategy: %s", destFile.string(), conflict_resolution_);
 
     if (conflict_resolution_ == CONFLICT_RESOLUTION_STRATEGY_REPLACE) {
       putFile(session, flowFile, tmpFile, destFile, directory);
@@ -168,57 +168,26 @@ void PutFile::onTrigger(core::ProcessContext *context, core::ProcessSession *ses
   }
 }
 
-std::string PutFile::tmpWritePath(const std::string &filename, const std::string &directory) {
+std::filesystem::path PutFile::tmpWritePath(const std::filesystem::path& filename, const std::filesystem::path& directory) {
   utils::Identifier tmpFileUuid = id_generator_->generate();
-  std::stringstream tmpFileSs;
-  tmpFileSs << directory;
-  auto lastSeparatorPos = filename.find_last_of(utils::file::get_separator());
-
-  if (lastSeparatorPos == std::string::npos) {
-    tmpFileSs << utils::file::get_separator() << "." << filename;
-  } else {
-    tmpFileSs << utils::file::get_separator() << filename.substr(0, lastSeparatorPos) << utils::file::get_separator() << "." << filename.substr(lastSeparatorPos + 1);
-  }
-
-  tmpFileSs << "." << tmpFileUuid.to_string();
-  std::string tmpFile = tmpFileSs.str();
-  return tmpFile;
+  auto new_filename = std::filesystem::path("." + filename.filename().string());
+  new_filename += "." + tmpFileUuid.to_string();
+  return (directory / filename.parent_path() / new_filename);
 }
 
-bool PutFile::putFile(core::ProcessSession *session, const std::shared_ptr<core::FlowFile>& flowFile, const std::string &tmpFile, const std::string &destFile, const std::string &destDir) {
+bool PutFile::putFile(core::ProcessSession *session,
+                      const std::shared_ptr<core::FlowFile>& flowFile,
+                      const std::filesystem::path& tmpFile,
+                      const std::filesystem::path& destFile,
+                      const std::filesystem::path& destDir) {
   if (!utils::file::exists(destDir) && try_mkdirs_) {
-    // Attempt to create directories in file's path
-    std::stringstream dir_path_stream;
-
-    logger_->log_debug("Destination directory does not exist; will attempt to create: ", destDir);
-    size_t i = 0;
-    auto pos = destFile.find(utils::file::get_separator());
-
-    while (pos != std::string::npos) {
-      auto dir_path_component = destFile.substr(i, pos - i);
-      dir_path_stream << dir_path_component;
-      auto dir_path = dir_path_stream.str();
-
-      if (!dir_path_component.empty()) {
-        logger_->log_debug("Attempting to create directory if it does not already exist: %s", dir_path);
-        if (!utils::file::exists(dir_path)) {
-          utils::file::create_dir(dir_path, false);
+    logger_->log_debug("Destination directory does not exist; will attempt to create: %s", destDir.string());
+    utils::file::create_dir(destDir, true);
 #ifndef WIN32
-          if (directory_permissions_.valid()) {
-            utils::file::set_permissions(dir_path, directory_permissions_.getValue());
-          }
-#endif
-        }
-
-        dir_path_stream << utils::file::get_separator();
-      } else if (pos == 0) {
-        // Support absolute paths
-        dir_path_stream << utils::file::get_separator();
-      }
-
-      i = pos + 1;
-      pos = destFile.find(utils::file::get_separator(), pos + 1);
+    if (directory_permissions_.valid()) {
+      utils::file::set_permissions(destDir, directory_permissions_.getValue());
     }
+#endif
   }
 
   bool success = false;
@@ -226,12 +195,12 @@ bool PutFile::putFile(core::ProcessSession *session, const std::shared_ptr<core:
   if (flowFile->getSize() > 0) {
     ReadCallback cb(tmpFile, destFile);
     session->read(flowFile, std::ref(cb));
-    logger_->log_debug("Committing %s", destFile);
+    logger_->log_debug("Committing %s", destFile.string());
     success = cb.commit();
   } else {
     std::ofstream outfile(destFile, std::ios::out | std::ios::binary);
     if (!outfile.good()) {
-      logger_->log_error("Failed to create empty file: %s", destFile);
+      logger_->log_error("Failed to create empty file: %s", destFile.string());
     } else {
       success = true;
     }
@@ -290,7 +259,7 @@ void PutFile::getDirectoryPermissions(core::ProcessContext *context) {
 }
 #endif
 
-PutFile::ReadCallback::ReadCallback(std::string tmp_file, std::string dest_file)
+PutFile::ReadCallback::ReadCallback(std::filesystem::path tmp_file, std::filesystem::path dest_file)
     : tmp_file_(std::move(tmp_file)),
       dest_file_(std::move(dest_file)) {
 }
@@ -326,17 +295,19 @@ int64_t PutFile::ReadCallback::operator()(const std::shared_ptr<io::InputStream>
 bool PutFile::ReadCallback::commit() {
   bool success = false;
 
-  logger_->log_info("PutFile committing put file operation to %s", dest_file_);
+  logger_->log_info("PutFile committing put file operation to %s", dest_file_.string());
 
   if (write_succeeded_) {
-    if (rename(tmp_file_.c_str(), dest_file_.c_str())) {
-      logger_->log_info("PutFile commit put file operation to %s failed because rename() call failed", dest_file_);
+    std::error_code rename_error;
+    std::filesystem::rename(tmp_file_, dest_file_, rename_error);
+    if (rename_error) {
+      logger_->log_info("PutFile commit put file operation to %s failed because std::filesystem::rename call failed", dest_file_.string());
     } else {
       success = true;
-      logger_->log_info("PutFile commit put file operation to %s succeeded", dest_file_);
+      logger_->log_info("PutFile commit put file operation to %s succeeded", dest_file_.string());
     }
   } else {
-    logger_->log_error("PutFile commit put file operation to %s failed because write failed", dest_file_);
+    logger_->log_error("PutFile commit put file operation to %s failed because write failed", dest_file_.string());
   }
 
   return success;
@@ -345,7 +316,7 @@ bool PutFile::ReadCallback::commit() {
 // Clean up resources
 PutFile::ReadCallback::~ReadCallback() {
   // Clean up tmp file, if necessary
-  std::remove(tmp_file_.c_str());
+  std::filesystem::remove(tmp_file_);
 }
 
 REGISTER_RESOURCE(PutFile, Processor);
diff --git a/extensions/standard-processors/processors/PutFile.h b/extensions/standard-processors/processors/PutFile.h
index 4ee674cd1..f19e74699 100644
--- a/extensions/standard-processors/processors/PutFile.h
+++ b/extensions/standard-processors/processors/PutFile.h
@@ -85,7 +85,7 @@ class PutFile : public core::Processor {
 
   class ReadCallback {
    public:
-    ReadCallback(std::string tmp_file, std::string dest_file);
+    ReadCallback(std::filesystem::path tmp_file, std::filesystem::path dest_file);
     ~ReadCallback();
     int64_t operator()(const std::shared_ptr<io::InputStream>& stream);
     bool commit();
@@ -93,8 +93,8 @@ class PutFile : public core::Processor {
    private:
     std::shared_ptr<core::logging::Logger> logger_{ core::logging::LoggerFactory<PutFile::ReadCallback>::getLogger() };
     bool write_succeeded_ = false;
-    std::string tmp_file_;
-    std::string dest_file_;
+    std::filesystem::path tmp_file_;
+    std::filesystem::path dest_file_;
   };
 
   /**
@@ -103,7 +103,7 @@ class PutFile : public core::Processor {
    * @param filename from which to generate temporary write file path
    * @return
    */
-  static std::string tmpWritePath(const std::string &filename, const std::string &directory);
+  static std::filesystem::path tmpWritePath(const std::filesystem::path& filename, const std::filesystem::path& directory);
 
  private:
   std::string conflict_resolution_;
@@ -112,9 +112,9 @@ class PutFile : public core::Processor {
 
   bool putFile(core::ProcessSession *session,
                const std::shared_ptr<core::FlowFile>& flowFile,
-               const std::string &tmpFile,
-               const std::string &destFile,
-               const std::string &destDir);
+               const std::filesystem::path& tmpFile,
+               const std::filesystem::path& destFile,
+               const std::filesystem::path& destDir);
   std::shared_ptr<core::logging::Logger> logger_ = core::logging::LoggerFactory<PutFile>::getLogger();
   static std::shared_ptr<utils::IdGenerator> id_generator_;
 
diff --git a/extensions/standard-processors/processors/PutTCP.cpp b/extensions/standard-processors/processors/PutTCP.cpp
index 92b8b020b..0b992ebf6 100644
--- a/extensions/standard-processors/processors/PutTCP.cpp
+++ b/extensions/standard-processors/processors/PutTCP.cpp
@@ -429,12 +429,12 @@ nonstd::expected<std::shared_ptr<TcpSocket>, std::error_code> ConnectionHandler<
 asio::ssl::context getSslContext(const auto& ssl_context_service) {
   gsl_Expects(ssl_context_service);
   asio::ssl::context ssl_context(asio::ssl::context::sslv23);
-  ssl_context.load_verify_file(ssl_context_service->getCACertificate());
+  ssl_context.load_verify_file(ssl_context_service->getCACertificate().string());
   ssl_context.set_verify_mode(asio::ssl::verify_peer);
   if (auto cert_file = ssl_context_service->getCertificateFile(); !cert_file.empty())
-    ssl_context.use_certificate_file(cert_file, asio::ssl::context::pem);
+    ssl_context.use_certificate_file(cert_file.string(), asio::ssl::context::pem);
   if (auto private_key_file = ssl_context_service->getPrivateKeyFile(); !private_key_file.empty())
-    ssl_context.use_private_key_file(private_key_file, asio::ssl::context::pem);
+    ssl_context.use_private_key_file(private_key_file.string(), asio::ssl::context::pem);
   ssl_context.set_password_callback([password = ssl_context_service->getPassphrase()](std::size_t&, asio::ssl::context_base::password_purpose&) { return password; });
   return ssl_context;
 }
diff --git a/extensions/standard-processors/processors/TailFile.cpp b/extensions/standard-processors/processors/TailFile.cpp
index abfd6784b..97bcfa04a 100644
--- a/extensions/standard-processors/processors/TailFile.cpp
+++ b/extensions/standard-processors/processors/TailFile.cpp
@@ -186,28 +186,27 @@ std::string parseDelimiter(const std::string &input) {
   }
 }
 
-std::map<std::string, TailState> update_keys_in_legacy_states(const std::map<std::string, TailState> &legacy_tail_states) {
-  std::map<std::string, TailState> new_tail_states;
+std::map<std::filesystem::path, TailState> update_keys_in_legacy_states(const std::map<std::filesystem::path, TailState> &legacy_tail_states) {
+  std::map<std::filesystem::path, TailState> new_tail_states;
   for (const auto &key_value_pair : legacy_tail_states) {
     const TailState &state = key_value_pair.second;
-    std::string full_file_name = utils::file::concat_path(state.path_, state.file_name_);
-    new_tail_states.emplace(full_file_name, state);
+    new_tail_states.emplace(state.path_ / state.file_name_, state);
   }
   return new_tail_states;
 }
 
-void openFile(const std::string &file_name, uint64_t offset, std::ifstream &input_stream, const std::shared_ptr<core::logging::Logger> &logger) {
-  logger->log_debug("Opening %s", file_name);
-  input_stream.open(file_name.c_str(), std::fstream::in | std::fstream::binary);
+void openFile(const std::filesystem::path& file_path, uint64_t offset, std::ifstream &input_stream, const std::shared_ptr<core::logging::Logger> &logger) {
+  logger->log_debug("Opening %s", file_path.string());
+  input_stream.open(file_path, std::fstream::in | std::fstream::binary);
   if (!input_stream.is_open() || !input_stream.good()) {
     input_stream.close();
-    throw Exception(FILE_OPERATION_EXCEPTION, "Could not open file: " + file_name);
+    throw Exception(FILE_OPERATION_EXCEPTION, "Could not open file: " + file_path.string());
   }
   if (offset != 0U) {
     input_stream.seekg(offset, std::ifstream::beg);
     if (!input_stream.good()) {
-      logger->log_error("Seeking to %" PRIu64 " failed for file %s (does file/filesystem support seeking?)", offset, file_name);
-      throw Exception(FILE_OPERATION_EXCEPTION, "Could not seek file " + file_name + " to offset " + std::to_string(offset));
+      logger->log_error("Seeking to %" PRIu64 " failed for file %s (does file/filesystem support seeking?)", offset, file_path.string());
+      throw Exception(FILE_OPERATION_EXCEPTION, "Could not seek file " + file_path.string() + " to offset " + std::to_string(offset));
     }
   }
 }
@@ -216,13 +215,13 @@ constexpr std::size_t BUFFER_SIZE = 4096;
 
 class FileReaderCallback {
  public:
-  FileReaderCallback(const std::string &file_name,
+  FileReaderCallback(const std::filesystem::path& file_path,
                      uint64_t offset,
                      char input_delimiter,
                      uint64_t checksum)
     : input_delimiter_(input_delimiter),
       checksum_(checksum) {
-    openFile(file_name, offset, input_stream_, logger_);
+    openFile(file_path, offset, input_stream_, logger_);
   }
 
   int64_t operator()(const std::shared_ptr<io::OutputStream>& output_stream) {
@@ -278,7 +277,7 @@ class FileReaderCallback {
   std::ifstream input_stream_;
   std::shared_ptr<core::logging::Logger> logger_ = core::logging::LoggerFactory<TailFile>::getLogger();
 
-  std::array<char, BUFFER_SIZE> buffer_;
+  std::array<char, BUFFER_SIZE> buffer_{};
   char *begin_ = buffer_.data();
   char *end_ = buffer_.data();
 
@@ -287,11 +286,11 @@ class FileReaderCallback {
 
 class WholeFileReaderCallback {
  public:
-  WholeFileReaderCallback(const std::string &file_name,
+  WholeFileReaderCallback(const std::filesystem::path& file_path,
                           uint64_t offset,
                           uint64_t checksum)
     : checksum_(checksum) {
-    openFile(file_name, offset, input_stream_, logger_);
+    openFile(file_path, offset, input_stream_, logger_);
   }
 
   uint64_t checksum() const {
@@ -299,7 +298,7 @@ class WholeFileReaderCallback {
   }
 
   int64_t operator()(const std::shared_ptr<io::OutputStream>& output_stream) {
-    std::array<char, BUFFER_SIZE> buffer;
+    std::array<char, BUFFER_SIZE> buffer{};
 
     io::CRCStream<io::OutputStream> crc_stream{gsl::make_not_null(output_stream.get()), checksum_};
 
@@ -350,18 +349,22 @@ void TailFile::onSchedule(const std::shared_ptr<core::ProcessContext> &context,
     delimiter_ = parseDelimiter(value);
   }
 
-  context->getProperty(FileName.getName(), file_to_tail_);
+  std::string file_name_str;
+  context->getProperty(FileName.getName(), file_name_str);
 
   std::string mode;
   context->getProperty(TailMode.getName(), mode);
 
   if (mode == "Multiple file") {
     tail_mode_ = Mode::MULTIPLE;
+    pattern_regex_ = utils::Regex(file_name_str);
 
     parseAttributeProviderServiceProperty(*context);
 
-    if (!context->getProperty(BaseDirectory.getName(), base_dir_)) {
+    if (auto base_dir = context->getProperty(BaseDirectory); !base_dir) {
       throw minifi::Exception(ExceptionType::PROCESSOR_EXCEPTION, "Base directory is required for multiple tail mode.");
+    } else {
+      base_dir_ = std::filesystem::path(*base_dir);
     }
 
     if (!attribute_provider_service_ && !utils::file::is_directory(base_dir_)) {
@@ -385,12 +388,11 @@ void TailFile::onSchedule(const std::shared_ptr<core::ProcessContext> &context,
 
   } else {
     tail_mode_ = Mode::SINGLE;
+    auto file_to_tail = std::filesystem::path(file_name_str);
 
-    std::string path;
-    std::string file_name;
-    if (utils::file::getFileNameAndPath(file_to_tail_, path, file_name)) {
+    if (file_to_tail.has_filename() && file_to_tail.has_parent_path()) {
       // NOTE: position and checksum will be updated in recoverState() if there is a persisted state for this file
-      tail_states_.emplace(file_to_tail_, TailState{path, file_name});
+      tail_states_.emplace(file_to_tail, TailState{ file_to_tail.parent_path(), file_to_tail.filename()});
     } else {
       throw minifi::Exception(ExceptionType::PROCESSOR_EXCEPTION, "File to tail must be a fully qualified file");
     }
@@ -427,7 +429,7 @@ void TailFile::parseAttributeProviderServiceProperty(core::ProcessContext& conte
   }
 }
 
-void TailFile::parseStateFileLine(char *buf, std::map<std::string, TailState> &state) const {
+void TailFile::parseStateFileLine(char *buf, std::map<std::filesystem::path, TailState> &state) const {
   char *line = buf;
 
   logger_->log_trace("Received line %s", buf);
@@ -462,13 +464,12 @@ void TailFile::parseStateFileLine(char *buf, std::map<std::string, TailState> &s
   value = utils::StringUtils::trimRight(value);
 
   if (key == "FILENAME") {
-    std::string fileLocation;
-    std::string fileName;
-    if (utils::file::getFileNameAndPath(value, fileLocation, fileName)) {
-      logger_->log_debug("State migration received path %s, file %s", fileLocation, fileName);
-      state.emplace(fileName, TailState{fileLocation, fileName});
+    std::filesystem::path file_path = value;
+    if (file_path.has_filename() && file_path.has_parent_path()) {
+      logger_->log_debug("State migration received path %s, file %s", file_path.parent_path().string(), file_path.filename().string());
+      state.emplace(file_path.filename(), TailState{file_path.parent_path(), file_path.filename()});
     } else {
-      state.emplace(value, TailState{fileLocation, value});
+      state.emplace(value, TailState{file_path.parent_path(), value});
     }
   }
   if (key == "POSITION") {
@@ -482,11 +483,10 @@ void TailFile::parseStateFileLine(char *buf, std::map<std::string, TailState> &s
   }
   if (key.find(CURRENT_STR) == 0) {
     const auto file = key.substr(strlen(CURRENT_STR));
-    std::string fileLocation;
-    std::string fileName;
-    if (utils::file::getFileNameAndPath(value, fileLocation, fileName)) {
-      state[file].path_ = fileLocation;
-      state[file].file_name_ = fileName;
+    std::filesystem::path file_path = value;
+    if (file_path.has_filename() && file_path.has_parent_path()) {
+      state[file].path_ = file_path.parent_path();
+      state[file].file_name_ = file_path.filename();
     } else {
       throw minifi::Exception(ExceptionType::PROCESSOR_EXCEPTION, "State file contains an invalid file name");
     }
@@ -499,7 +499,7 @@ void TailFile::parseStateFileLine(char *buf, std::map<std::string, TailState> &s
 }
 
 bool TailFile::recoverState(const std::shared_ptr<core::ProcessContext>& context) {
-  std::map<std::string, TailState> new_tail_states;
+  std::map<std::filesystem::path, TailState> new_tail_states;
   bool state_load_success = getStateFromStateManager(new_tail_states) ||
                             getStateFromLegacyStateFile(context, new_tail_states);
   if (!state_load_success) {
@@ -527,7 +527,7 @@ bool TailFile::recoverState(const std::shared_ptr<core::ProcessContext>& context
   return true;
 }
 
-bool TailFile::getStateFromStateManager(std::map<std::string, TailState> &new_tail_states) const {
+bool TailFile::getStateFromStateManager(std::map<std::filesystem::path, TailState> &new_tail_states) const {
   std::unordered_map<std::string, std::string> state_map;
   if (state_manager_->get(state_map)) {
     for (size_t i = 0U;; ++i) {
@@ -542,13 +542,12 @@ bool TailFile::getStateFromStateManager(std::map<std::string, TailState> &new_ta
             readOptionalInt64(state_map, "file." + std::to_string(i) + ".last_read_time")
         }};
 
-        std::string fileLocation;
-        std::string fileName;
-        if (utils::file::getFileNameAndPath(current, fileLocation, fileName)) {
-          logger_->log_debug("Received path %s, file %s", fileLocation, fileName);
-          new_tail_states.emplace(current, TailState{fileLocation, fileName, position, last_read_time, checksum});
+        std::filesystem::path file_path = current;
+        if (file_path.has_filename() && file_path.has_parent_path()) {
+          logger_->log_debug("Received path %s, file %s", file_path.parent_path().string(), file_path.filename().string());
+          new_tail_states.emplace(current, TailState{file_path.parent_path(), file_path.filename(), position, last_read_time, checksum});
         } else {
-          new_tail_states.emplace(current, TailState{fileLocation, current, position, last_read_time, checksum});
+          new_tail_states.emplace(current, TailState{file_path.parent_path(), file_path, position, last_read_time, checksum});
         }
       } catch (...) {
         continue;
@@ -556,7 +555,7 @@ bool TailFile::getStateFromStateManager(std::map<std::string, TailState> &new_ta
     }
     for (const auto& s : tail_states_) {
       logger_->log_debug("TailState %s: %s, %s, %" PRIu64 ", %" PRIu64,
-                         s.first, s.second.path_, s.second.file_name_, s.second.position_, s.second.checksum_);
+                         s.first.string(), s.second.path_.string(), s.second.file_name_.string(), s.second.position_, s.second.checksum_);
     }
     return true;
   } else {
@@ -566,7 +565,7 @@ bool TailFile::getStateFromStateManager(std::map<std::string, TailState> &new_ta
 }
 
 bool TailFile::getStateFromLegacyStateFile(const std::shared_ptr<core::ProcessContext>& context,
-                                           std::map<std::string, TailState> &new_tail_states) const {
+                                           std::map<std::filesystem::path, TailState> &new_tail_states) const {
   std::string state_file_name_property;
   context->getProperty(StateFile.getName(), state_file_name_property);
   std::string state_file = state_file_name_property + "." + getUUIDStr();
@@ -577,7 +576,7 @@ bool TailFile::getStateFromLegacyStateFile(const std::shared_ptr<core::ProcessCo
     return false;
   }
 
-  std::map<std::string, TailState> legacy_tail_states;
+  std::map<std::filesystem::path, TailState> legacy_tail_states;
   char buf[BUFFER_SIZE];
   for (file.getline(buf, BUFFER_SIZE); file.good(); file.getline(buf, BUFFER_SIZE)) {
     parseStateFileLine(buf, legacy_tail_states);
@@ -606,8 +605,8 @@ bool TailFile::storeState() {
   std::unordered_map<std::string, std::string> state;
   size_t i = 0;
   for (const auto& tail_state : tail_states_) {
-    state["file." + std::to_string(i) + ".current"] = tail_state.first;
-    state["file." + std::to_string(i) + ".name"] = tail_state.second.file_name_;
+    state["file." + std::to_string(i) + ".current"] = tail_state.first.string();
+    state["file." + std::to_string(i) + ".name"] = tail_state.second.file_name_.string();
     state["file." + std::to_string(i) + ".position"] = std::to_string(tail_state.second.position_);
     state["file." + std::to_string(i) + ".checksum"] = std::to_string(tail_state.second.checksum_);
     state["file." + std::to_string(i) + ".last_read_time"] = std::to_string(tail_state.second.lastReadTimeInMilliseconds());
@@ -621,8 +620,8 @@ bool TailFile::storeState() {
 }
 
 std::string TailFile::parseRollingFilePattern(const TailState &state) const {
-  std::size_t last_dot_position = state.file_name_.find_last_of('.');
-  std::string base_name = state.file_name_.substr(0, last_dot_position);
+  std::size_t last_dot_position = state.file_name_.string().find_last_of('.');
+  std::string base_name = state.file_name_.string().substr(0, last_dot_position);
   return utils::StringUtils::replaceOne(rolling_filename_pattern_, "${filename}", base_name);
 }
 
@@ -632,12 +631,12 @@ std::vector<TailState> TailFile::findAllRotatedFiles(const TailState &state) con
   std::string pattern = parseRollingFilePattern(state);
 
   std::vector<TailStateWithMtime> matched_files_with_mtime;
-  auto collect_matching_files = [&](const std::string &path, const std::string &file_name) -> bool {
+  auto collect_matching_files = [&](const std::filesystem::path& path, const std::filesystem::path& file_name) -> bool {
     utils::Regex pattern_regex(pattern);
-    if (file_name != state.file_name_ && utils::regexMatch(file_name, pattern_regex)) {
-      std::string full_file_name = path + utils::file::get_separator() + file_name;
+    if (file_name != state.file_name_ && utils::regexMatch(file_name.string(), pattern_regex)) {
+      auto full_file_name = path / file_name;
       TailStateWithMtime::TimePoint mtime{utils::file::last_write_time_point(full_file_name)};
-      logger_->log_debug("File %s with mtime %" PRId64 " matches rolling filename pattern %s, so we are reading it", file_name, int64_t{mtime.time_since_epoch().count()}, pattern);
+      logger_->log_debug("File %s with mtime %" PRId64 " matches rolling filename pattern %s, so we are reading it", file_name.string(), int64_t{mtime.time_since_epoch().count()}, pattern);
       matched_files_with_mtime.emplace_back(TailState{path, file_name}, mtime);
     }
     return true;
@@ -654,14 +653,14 @@ std::vector<TailState> TailFile::findRotatedFilesAfterLastReadTime(const TailSta
   std::string pattern = parseRollingFilePattern(state);
 
   std::vector<TailStateWithMtime> matched_files_with_mtime;
-  auto collect_matching_files = [&](const std::string &path, const std::string &file_name) -> bool {
+  auto collect_matching_files = [&](const std::filesystem::path& path, const std::filesystem::path& file_name) -> bool {
     utils::Regex pattern_regex(pattern);
-    if (file_name != state.file_name_ && utils::regexMatch(file_name, pattern_regex)) {
-      std::string full_file_name = path + utils::file::get_separator() + file_name;
+    if (file_name != state.file_name_ && utils::regexMatch(file_name.string(), pattern_regex)) {
+      auto full_file_name = path / file_name;
       TailStateWithMtime::TimePoint mtime{utils::file::last_write_time_point(full_file_name)};
-      logger_->log_debug("File %s with mtime %" PRId64 " matches rolling filename pattern %s", file_name, int64_t{mtime.time_since_epoch().count()}, pattern);
+      logger_->log_debug("File %s with mtime %" PRId64 " matches rolling filename pattern %s", file_name.string(), int64_t{mtime.time_since_epoch().count()}, pattern);
       if (mtime >= std::chrono::time_point_cast<std::chrono::seconds>(state.last_read_time_)) {
-        logger_->log_debug("File %s has mtime >= last read time, so we are going to read it", file_name);
+        logger_->log_debug("File %s has mtime >= last read time, so we are going to read it", file_name.string());
         matched_files_with_mtime.emplace_back(TailState{path, file_name}, mtime);
       }
     }
@@ -682,7 +681,7 @@ std::vector<TailState> TailFile::sortAndSkipMainFilePrefix(const TailState &stat
 
   if (!matched_files_with_mtime.empty() && state.position_ > 0) {
     TailState &first_rotated_file = matched_files_with_mtime[0].tail_state_;
-    std::string full_file_name = first_rotated_file.fileNameWithPath();
+    auto full_file_name = first_rotated_file.fileNameWithPath();
     if (utils::file::file_size(full_file_name) >= state.position_) {
       uint64_t checksum = utils::file::computeChecksum(full_file_name, state.position_);
       if (checksum == state.checksum_) {
@@ -729,7 +728,7 @@ bool TailFile::isOldFileInitiallyRead(TailState &state) const {
 }
 
 void TailFile::processFile(const std::shared_ptr<core::ProcessSession> &session,
-                           const std::string &full_file_name,
+                           const std::filesystem::path& full_file_name,
                            TailState &state) {
   if (isOldFileInitiallyRead(state)) {
     if (initial_start_position_ == InitialStartPositions::BEGINNING_OF_TIME) {
@@ -746,7 +745,7 @@ void TailFile::processFile(const std::shared_ptr<core::ProcessSession> &session,
     if (fsize < state.position_) {
       processRotatedFilesAfterLastReadTime(session, state);
     } else if (fsize == state.position_) {
-      logger_->log_trace("Skipping file %s as its size hasn't changed since last read", state.file_name_);
+      logger_->log_trace("Skipping file %s as its size hasn't changed since last read", state.file_name_.string());
       return;
     }
   }
@@ -774,19 +773,20 @@ void TailFile::processRotatedFiles(const std::shared_ptr<core::ProcessSession> &
 }
 
 void TailFile::processSingleFile(const std::shared_ptr<core::ProcessSession> &session,
-                                 const std::string &full_file_name,
+                                 const std::filesystem::path& full_file_name,
                                  TailState &state) {
-  std::string fileName = state.file_name_;
+  auto fileName = state.file_name_;
 
   if (utils::file::file_size(full_file_name) == 0U) {
-    logger_->log_warn("Unable to read file %s as it does not exist or has size zero", full_file_name);
+    logger_->log_warn("Unable to read file %s as it does not exist or has size zero", full_file_name.string());
     return;
   }
-  logger_->log_debug("Tailing file %s from %" PRIu64, full_file_name, state.position_);
+  logger_->log_debug("Tailing file %s from %" PRIu64, full_file_name.string(), state.position_);
 
-  std::size_t last_dot_position = fileName.find_last_of('.');
-  std::string baseName = fileName.substr(0, last_dot_position);
-  std::string extension = fileName.substr(last_dot_position + 1);
+  std::string baseName = fileName.stem().string();
+  std::string extension = fileName.extension().string();
+  if (extension.starts_with('.'))
+    extension.erase(extension.begin());
 
   if (!delimiter_.empty()) {
     char delim = delimiter_[0];
@@ -826,26 +826,26 @@ void TailFile::processSingleFile(const std::shared_ptr<core::ProcessSession> &se
   }
 }
 
-void TailFile::updateFlowFileAttributes(const std::string &full_file_name, const TailState &state,
-                                        const std::string &fileName, const std::string &baseName,
-                                        const std::string &extension,
+void TailFile::updateFlowFileAttributes(const std::filesystem::path& full_file_name, const TailState& state,
+                                        const std::filesystem::path& fileName, const std::string& baseName,
+                                        const std::string& extension,
                                         std::shared_ptr<core::FlowFile> &flow_file) const {
-  logger_->log_info("TailFile %s for %" PRIu64 " bytes", fileName, flow_file->getSize());
+  logger_->log_info("TailFile %s for %" PRIu64 " bytes", fileName.string(), flow_file->getSize());
   std::string logName = textfragmentutils::createFileName(baseName, extension, state.position_, flow_file->getSize());
-  flow_file->setAttribute(core::SpecialFlowAttribute::PATH, state.path_);
-  flow_file->addAttribute(core::SpecialFlowAttribute::ABSOLUTE_PATH, full_file_name);
+  flow_file->setAttribute(core::SpecialFlowAttribute::PATH, state.path_.string());
+  flow_file->addAttribute(core::SpecialFlowAttribute::ABSOLUTE_PATH, full_file_name.string());
   flow_file->setAttribute(core::SpecialFlowAttribute::FILENAME, logName);
 
   flow_file->setAttribute(textfragmentutils::BASE_NAME_ATTRIBUTE, baseName);
   flow_file->setAttribute(textfragmentutils::POST_NAME_ATTRIBUTE, extension);
   flow_file->setAttribute(textfragmentutils::OFFSET_ATTRIBUTE, std::to_string(state.position_));
 
-  if (extra_attributes_.contains(state.path_)) {
+  if (extra_attributes_.contains(state.path_.string())) {
     std::string prefix;
     if (attribute_provider_service_) {
       prefix = std::string(attribute_provider_service_->name()) + ".";
     }
-    for (const auto& [key, value] : extra_attributes_.at(state.path_)) {
+    for (const auto& [key, value] : extra_attributes_.at(state.path_.string())) {
       flow_file->setAttribute(prefix + key, value);
     }
   }
@@ -864,14 +864,14 @@ void TailFile::doMultifileLookup(core::ProcessContext& context) {
 }
 
 void TailFile::checkForRemovedFiles() {
-  std::vector<std::string> file_names_to_remove;
+  gsl_Expects(pattern_regex_);
+  std::vector<std::filesystem::path> file_names_to_remove;
 
   for (const auto &kv : tail_states_) {
-    const std::string &full_file_name = kv.first;
+    const auto& full_file_name = kv.first;
     const TailState &state = kv.second;
-    utils::Regex pattern_regex(file_to_tail_);
     if (utils::file::file_size(state.fileNameWithPath()) == 0U ||
-        !utils::regexMatch(state.file_name_, pattern_regex)) {
+        !utils::regexMatch(state.file_name_.string(), *pattern_regex_)) {
       file_names_to_remove.push_back(full_file_name);
     }
   }
@@ -882,10 +882,10 @@ void TailFile::checkForRemovedFiles() {
 }
 
 void TailFile::checkForNewFiles(core::ProcessContext& context) {
-  auto add_new_files_callback = [&](const std::string &path, const std::string &file_name) -> bool {
-    std::string full_file_name = path + utils::file::get_separator() + file_name;
-    utils::Regex file_to_tail_regex(file_to_tail_);
-    if (!containsKey(tail_states_, full_file_name) && utils::regexMatch(file_name, file_to_tail_regex)) {
+  gsl_Expects(pattern_regex_);
+  auto add_new_files_callback = [&](const std::filesystem::path& path, const std::filesystem::path& file_name) -> bool {
+    auto full_file_name = path / file_name;
+    if (!containsKey(tail_states_, full_file_name) && utils::regexMatch(file_name.string(), *pattern_regex_)) {
       tail_states_.emplace(full_file_name, TailState{path, file_name});
     }
     return true;
diff --git a/extensions/standard-processors/processors/TailFile.h b/extensions/standard-processors/processors/TailFile.h
index 0523ad73e..4cbab4759 100644
--- a/extensions/standard-processors/processors/TailFile.h
+++ b/extensions/standard-processors/processors/TailFile.h
@@ -36,30 +36,31 @@
 #include "core/logging/LoggerConfiguration.h"
 #include "utils/Enum.h"
 #include "utils/Export.h"
+#include "utils/RegexUtils.h"
 
 namespace org::apache::nifi::minifi::processors {
 
 struct TailState {
-  TailState(std::string path, std::string file_name, uint64_t position,
+  TailState(std::filesystem::path path, std::filesystem::path file_name, uint64_t position,
             std::chrono::file_clock::time_point last_read_time,
             uint64_t checksum)
       : path_(std::move(path)), file_name_(std::move(file_name)), position_(position), last_read_time_(last_read_time), checksum_(checksum) {}
 
-  TailState(std::string path, std::string file_name)
+  TailState(std::filesystem::path path, std::filesystem::path file_name)
       : TailState{std::move(path), std::move(file_name), 0, std::chrono::file_clock::time_point{}, 0} {}
 
   TailState() = default;
 
-  std::string fileNameWithPath() const {
-    return path_ + utils::file::get_separator() + file_name_;
+  [[nodiscard]] std::filesystem::path fileNameWithPath() const {
+    return path_ / file_name_;
   }
 
-  int64_t lastReadTimeInMilliseconds() const {
+  [[nodiscard]] int64_t lastReadTimeInMilliseconds() const {
     return std::chrono::duration_cast<std::chrono::milliseconds>(last_read_time_.time_since_epoch()).count();
   }
 
-  std::string path_;
-  std::string file_name_;
+  std::filesystem::path path_;
+  std::filesystem::path file_name_;
   uint64_t position_ = 0;
   std::chrono::file_clock::time_point last_read_time_;
   uint64_t checksum_ = 0;
@@ -75,7 +76,7 @@ SMART_ENUM(InitialStartPositions,
   (BEGINNING_OF_TIME, "Beginning of Time"),
   (BEGINNING_OF_FILE, "Beginning of File"),
   (CURRENT_TIME, "Current Time")
-);
+)
 
 class TailFile : public core::Processor {
  public:
@@ -168,7 +169,7 @@ class TailFile : public core::Processor {
   };
 
   void parseAttributeProviderServiceProperty(core::ProcessContext& context);
-  void parseStateFileLine(char *buf, std::map<std::string, TailState> &state) const;
+  void parseStateFileLine(char *buf, std::map<std::filesystem::path, TailState> &state) const;
   void processAllRotatedFiles(const std::shared_ptr<core::ProcessSession> &session, TailState &state);
   void processRotatedFiles(const std::shared_ptr<core::ProcessSession> &session, TailState &state, std::vector<TailState> &rotated_file_states);
   void processRotatedFilesAfterLastReadTime(const std::shared_ptr<core::ProcessSession> &session, TailState &state);
@@ -177,19 +178,19 @@ class TailFile : public core::Processor {
   std::vector<TailState> findRotatedFilesAfterLastReadTime(const TailState &state) const;
   static std::vector<TailState> sortAndSkipMainFilePrefix(const TailState &state, std::vector<TailStateWithMtime>& matched_files_with_mtime);
   void processFile(const std::shared_ptr<core::ProcessSession> &session,
-                   const std::string &full_file_name,
+                   const std::filesystem::path& full_file_name,
                    TailState &state);
   void processSingleFile(const std::shared_ptr<core::ProcessSession> &session,
-                         const std::string &full_file_name,
+                         const std::filesystem::path& full_file_name,
                          TailState &state);
-  bool getStateFromStateManager(std::map<std::string, TailState> &new_tail_states) const;
+  bool getStateFromStateManager(std::map<std::filesystem::path, TailState> &new_tail_states) const;
   bool getStateFromLegacyStateFile(const std::shared_ptr<core::ProcessContext>& context,
-                                   std::map<std::string, TailState> &new_tail_states) const;
+                                   std::map<std::filesystem::path, TailState> &new_tail_states) const;
   void doMultifileLookup(core::ProcessContext& context);
   void checkForRemovedFiles();
   void checkForNewFiles(core::ProcessContext& context);
   static std::string baseDirectoryFromAttributes(const controllers::AttributeProviderService::AttributeMap& attribute_map, core::ProcessContext& context);
-  void updateFlowFileAttributes(const std::string &full_file_name, const TailState &state, const std::string &fileName,
+  void updateFlowFileAttributes(const std::filesystem::path& full_file_name, const TailState &state, const std::filesystem::path& fileName,
                                 const std::string &baseName, const std::string &extension,
                                 std::shared_ptr<core::FlowFile> &flow_file) const;
   static void updateStateAttributes(TailState &state, uint64_t size, uint64_t checksum);
@@ -201,10 +202,10 @@ class TailFile : public core::Processor {
 
   std::string delimiter_;  // Delimiter for the data incoming from the tailed file.
   core::CoreComponentStateManager* state_manager_ = nullptr;
-  std::map<std::string, TailState> tail_states_;
+  std::map<std::filesystem::path, TailState> tail_states_;
   Mode tail_mode_ = Mode::UNDEFINED;
-  std::string file_to_tail_;
-  std::string base_dir_;
+  std::optional<utils::Regex> pattern_regex_;
+  std::filesystem::path base_dir_;
   bool recursive_lookup_ = false;
   std::chrono::milliseconds lookup_frequency_{};
   std::chrono::steady_clock::time_point last_multifile_lookup_;
diff --git a/extensions/standard-processors/tests/integration/SecureSocketGetTCPTest.cpp b/extensions/standard-processors/tests/integration/SecureSocketGetTCPTest.cpp
index 2aad110a9..dc28b261b 100644
--- a/extensions/standard-processors/tests/integration/SecureSocketGetTCPTest.cpp
+++ b/extensions/standard-processors/tests/integration/SecureSocketGetTCPTest.cpp
@@ -66,8 +66,7 @@ class SecureSocketTest : public IntegrationBase {
     LogTestController::getInstance().setTrace<minifi::io::TLSSocket>();
     LogTestController::getInstance().setTrace<minifi::processors::GetTCP>();
     std::fstream file;
-    ss << dir << utils::file::get_separator() << "tstFile.ext";
-    file.open(ss.str(), std::ios::out);
+    file.open(dir / "tstFile.ext", std::ios::out);
     file << "tempFile";
     file.close();
   }
@@ -128,8 +127,7 @@ class SecureSocketTest : public IntegrationBase {
  protected:
   bool isSecure;
   std::atomic<bool> isRunning_;
-  std::string dir;
-  std::stringstream ss;
+  std::filesystem::path dir;
   TestController testController;
   std::shared_ptr<org::apache::nifi::minifi::io::TLSServerSocket> server_socket_;
 };
diff --git a/extensions/standard-processors/tests/integration/TailFileTest.cpp b/extensions/standard-processors/tests/integration/TailFileTest.cpp
index 470c36566..0486c575f 100644
--- a/extensions/standard-processors/tests/integration/TailFileTest.cpp
+++ b/extensions/standard-processors/tests/integration/TailFileTest.cpp
@@ -38,11 +38,9 @@ class TailFileTestHarness : public IntegrationBase {
   TailFileTestHarness() {
     dir = testController.createTempDirectory();
 
-    statefile = dir + utils::file::get_separator();
-    statefile += "statefile";
+    statefile = dir / "statefile";
     std::fstream file;
-    ss << dir << utils::file::get_separator() << "tstFile.ext";
-    file.open(ss.str(), std::ios::out);
+    file.open(dir / "tstFile.ext", std::ios::out);
     file << "Lin\\e1\nli\\nen\nli\\ne3\nli\\ne4\nli\\ne5\n";
     file.close();
   }
@@ -57,8 +55,8 @@ class TailFileTestHarness : public IntegrationBase {
   }
 
   void cleanup() override {
-    std::remove(ss.str().c_str());
-    std::remove(statefile.c_str());
+    std::filesystem::remove(dir / "tstFile.ext");
+    std::filesystem::remove(statefile);
     IntegrationBase::cleanup();
   }
 
@@ -75,15 +73,14 @@ class TailFileTestHarness : public IntegrationBase {
     fc.executeOnComponent("tf", [this] (minifi::state::StateController& component) {
       auto proc = dynamic_cast<minifi::state::ProcessorController*>(&component);
       if (nullptr != proc) {
-        proc->getProcessor().setProperty(minifi::processors::TailFile::FileName, ss.str());
-        proc->getProcessor().setProperty(minifi::processors::TailFile::StateFile, statefile);
+        proc->getProcessor().setProperty(minifi::processors::TailFile::FileName, (dir / "tstFile.ext").string());
+        proc->getProcessor().setProperty(minifi::processors::TailFile::StateFile, statefile.string());
       }
     });
   }
 
-  std::string statefile;
-  std::string dir;
-  std::stringstream ss;
+  std::filesystem::path statefile;
+  std::filesystem::path dir;
   TestController testController;
 };
 
diff --git a/extensions/standard-processors/tests/unit/AttributesToJSONTests.cpp b/extensions/standard-processors/tests/unit/AttributesToJSONTests.cpp
index 4a08fcc20..11f82fd63 100644
--- a/extensions/standard-processors/tests/unit/AttributesToJSONTests.cpp
+++ b/extensions/standard-processors/tests/unit/AttributesToJSONTests.cpp
@@ -53,8 +53,8 @@ class AttributesToJSONTestFixture {
     logattribute_ = plan_->addProcessor("LogAttribute", "LogAttribute", core::Relationship("success", "description"), true);
     putfile_ = plan_->addProcessor("PutFile", "PutFile", core::Relationship("success", "description"), true);
 
-    plan_->setProperty(getfile_, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir_);
-    plan_->setProperty(putfile_, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), dir_);
+    plan_->setProperty(getfile_, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir_.string());
+    plan_->setProperty(putfile_, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), dir_.string());
 
     update_attribute_->setDynamicProperty("my_attribute", "my_value");
     update_attribute_->setDynamicProperty("my_attribute_1", "my_value_1");
@@ -93,8 +93,8 @@ class AttributesToJSONTestFixture {
   std::vector<std::string> getOutputFileContents() {
     std::vector<std::string> file_contents;
 
-    auto callback = [&file_contents](const std::string& path, const std::string& filename) -> bool {
-      std::ifstream is(path + utils::file::FileUtils::get_separator() + filename, std::ifstream::binary);
+    auto callback = [&file_contents](const std::filesystem::path& path, const std::filesystem::path& filename) -> bool {
+      std::ifstream is(path / filename, std::ifstream::binary);
       std::string file_content(std::istreambuf_iterator<char>{is}, std::istreambuf_iterator<char>{});
       file_contents.push_back(file_content);
       return true;
@@ -108,7 +108,7 @@ class AttributesToJSONTestFixture {
  protected:
   TestController test_controller_;
   std::shared_ptr<TestPlan> plan_;
-  std::string dir_;
+  std::filesystem::path dir_;
   std::shared_ptr<core::Processor> getfile_;
   std::shared_ptr<core::Processor> update_attribute_;
   std::shared_ptr<core::Processor> attribute_to_json_;
@@ -123,14 +123,14 @@ TEST_CASE_METHOD(AttributesToJSONTestFixture, "Move all attributes to a flowfile
   REQUIRE(file_contents[0] == TEST_FILE_CONTENT);
 
   const std::unordered_map<std::string, std::optional<std::string>> expected_attributes {
-    {"absolute.path", dir_ + utils::file::FileUtils::get_separator() + TEST_FILE_NAME},
+    {"absolute.path", (dir_ / TEST_FILE_NAME).string()},
     {"empty_attribute", ""},
     {"filename", TEST_FILE_NAME},
     {"flow.id", "test"},
     {"my_attribute", "my_value"},
     {"my_attribute_1", "my_value_1"},
     {"other_attribute", "other_value"},
-    {"path", dir_ + utils::file::FileUtils::get_separator()}
+    {"path", (dir_ / "").string()}
   };
   assertJSONAttributesFromLog(expected_attributes);
 }
@@ -184,14 +184,14 @@ TEST_CASE_METHOD(AttributesToJSONTestFixture, "JSON attributes are written in fl
   test_controller_.runSession(plan_);
 
   const std::unordered_map<std::string, std::optional<std::string>> expected_attributes {
-    {"absolute.path", dir_ + utils::file::FileUtils::get_separator() + TEST_FILE_NAME},
+    {"absolute.path", (dir_ / TEST_FILE_NAME).string()},
     {"empty_attribute", ""},
     {"filename", TEST_FILE_NAME},
     {"flow.id", "test"},
     {"my_attribute", "my_value"},
     {"my_attribute_1", "my_value_1"},
     {"other_attribute", "other_value"},
-    {"path", dir_ + utils::file::FileUtils::get_separator()}
+    {"path", (dir_ / "").string()}
   };
   assertJSONAttributesFromFile(expected_attributes);
 }
@@ -243,7 +243,7 @@ TEST_CASE_METHOD(AttributesToJSONTestFixture, "Attributes from attributes list a
     {"empty_attribute", ""},
     {"filename", TEST_FILE_NAME},
     {"my_attribute", "my_value"},
-    {"path", dir_ + utils::file::FileUtils::get_separator()}
+    {"path", (dir_ / "").string()}
   };
   assertJSONAttributesFromLog(expected_attributes);
 }
@@ -272,7 +272,7 @@ TEST_CASE_METHOD(AttributesToJSONTestFixture, "Core attributes are written if th
 
   const std::unordered_map<std::string, std::optional<std::string>> expected_attributes {
     {"filename", TEST_FILE_NAME},
-    {"path", dir_ + utils::file::FileUtils::get_separator()},
+    {"path", (dir_ / "").string()},
     {"my_attribute", "my_value"}
   };
   assertJSONAttributesFromLog(expected_attributes);
diff --git a/extensions/standard-processors/tests/unit/ExecuteProcessTests.cpp b/extensions/standard-processors/tests/unit/ExecuteProcessTests.cpp
index a40a47f66..59c1e8edb 100644
--- a/extensions/standard-processors/tests/unit/ExecuteProcessTests.cpp
+++ b/extensions/standard-processors/tests/unit/ExecuteProcessTests.cpp
@@ -52,7 +52,7 @@ TEST_CASE_METHOD(ExecuteProcessTestsFixture, "ExecuteProcess can run a single co
 }
 
 TEST_CASE_METHOD(ExecuteProcessTestsFixture, "ExecuteProcess can run an executable with a parameter", "[ExecuteProcess]") {
-  auto command = minifi::utils::file::concat_path(minifi::utils::file::get_executable_dir(), "EchoParameters");
+  auto command = minifi::utils::file::get_executable_dir() / "EchoParameters";
   std::string arguments = "0 test_data";
   REQUIRE(execute_process_->setProperty(processors::ExecuteProcess::Command, command));
   REQUIRE(execute_process_->setProperty(processors::ExecuteProcess::CommandArguments, arguments));
@@ -68,7 +68,7 @@ TEST_CASE_METHOD(ExecuteProcessTestsFixture, "ExecuteProcess can run an executab
 }
 
 TEST_CASE_METHOD(ExecuteProcessTestsFixture, "ExecuteProcess can run an executable with escaped parameters", "[ExecuteProcess]") {
-  auto command = minifi::utils::file::concat_path(minifi::utils::file::get_executable_dir(), "EchoParameters");
+  auto command = minifi::utils::file::get_executable_dir() / "EchoParameters";
   std::string arguments = R"(0 test_data test_data2 "test data 3" "\"test data 4\")";
   REQUIRE(execute_process_->setProperty(processors::ExecuteProcess::Command, command));
   REQUIRE(execute_process_->setProperty(processors::ExecuteProcess::CommandArguments, arguments));
@@ -84,7 +84,7 @@ TEST_CASE_METHOD(ExecuteProcessTestsFixture, "ExecuteProcess can run an executab
 }
 
 TEST_CASE_METHOD(ExecuteProcessTestsFixture, "ExecuteProcess does not produce a flowfile if no output is generated", "[ExecuteProcess]") {
-  auto command = minifi::utils::file::concat_path(minifi::utils::file::get_executable_dir(), "EchoParameters");
+  auto command = minifi::utils::file::get_executable_dir() / "EchoParameters";
   REQUIRE(execute_process_->setProperty(processors::ExecuteProcess::Command, command));
 
   controller_.plan->scheduleProcessor(execute_process_);
@@ -95,7 +95,7 @@ TEST_CASE_METHOD(ExecuteProcessTestsFixture, "ExecuteProcess does not produce a
 }
 
 TEST_CASE_METHOD(ExecuteProcessTestsFixture, "ExecuteProcess can redirect error stream to stdout", "[ExecuteProcess]") {
-  auto command = minifi::utils::file::concat_path(minifi::utils::file::get_executable_dir(), "EchoParameters");
+  auto command = minifi::utils::file::get_executable_dir() / "EchoParameters";
   REQUIRE(execute_process_->setProperty(processors::ExecuteProcess::Command, command));
   REQUIRE(execute_process_->setProperty(processors::ExecuteProcess::RedirectErrorStream, "true"));
 
@@ -127,7 +127,7 @@ TEST_CASE_METHOD(ExecuteProcessTestsFixture, "ExecuteProcess can change workdir"
 }
 
 TEST_CASE_METHOD(ExecuteProcessTestsFixture, "ExecuteProcess can forward long running output in batches", "[ExecuteProcess]") {
-  auto command = minifi::utils::file::concat_path(minifi::utils::file::get_executable_dir(), "EchoParameters");
+  auto command = minifi::utils::file::get_executable_dir() / "EchoParameters";
   std::string arguments = "100 test_data1 test_data2";
   REQUIRE(execute_process_->setProperty(processors::ExecuteProcess::Command, command));
   REQUIRE(execute_process_->setProperty(processors::ExecuteProcess::CommandArguments, arguments));
@@ -147,7 +147,7 @@ TEST_CASE_METHOD(ExecuteProcessTestsFixture, "ExecuteProcess can forward long ru
 }
 
 TEST_CASE_METHOD(ExecuteProcessTestsFixture, "ExecuteProcess buffer long outputs", "[ExecuteProcess]") {
-  auto command = minifi::utils::file::concat_path(minifi::utils::file::get_executable_dir(), "EchoParameters");
+  auto command = minifi::utils::file::get_executable_dir() / "EchoParameters";
   REQUIRE(execute_process_->setProperty(processors::ExecuteProcess::Command, command));
   std::string param1;
 
diff --git a/extensions/standard-processors/tests/unit/ExtractTextTests.cpp b/extensions/standard-processors/tests/unit/ExtractTextTests.cpp
index e4796a661..c3af9e539 100644
--- a/extensions/standard-processors/tests/unit/ExtractTextTests.cpp
+++ b/extensions/standard-processors/tests/unit/ExtractTextTests.cpp
@@ -70,7 +70,7 @@ TEST_CASE("Test usage of ExtractText", "[extracttextTest]") {
   auto temp_dir = testController.createTempDirectory();
   REQUIRE(!temp_dir.empty());
   std::shared_ptr<core::Processor> getfile = plan->addProcessor("GetFile", "getfileCreate2");
-  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), temp_dir);
+  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), temp_dir.string());
   plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::KeepSourceFile.getName(), "true");
 
   std::shared_ptr<core::Processor> maprocessor = plan->addProcessor("ExtractText", "testExtractText", core::Relationship("success", "description"), true);
@@ -79,9 +79,7 @@ TEST_CASE("Test usage of ExtractText", "[extracttextTest]") {
   std::shared_ptr<core::Processor> laprocessor = plan->addProcessor("LogAttribute", "outputLogAttribute", core::Relationship("success", "description"), true);
   plan->setProperty(laprocessor, org::apache::nifi::minifi::processors::LogAttribute::AttributesToLog.getName(), TEST_ATTR);
 
-  std::stringstream ss1;
-  ss1 << temp_dir << utils::file::get_separator() << TEST_FILE;
-  std::string test_file_path = ss1.str();
+  auto test_file_path = temp_dir / TEST_FILE;
 
   std::ofstream test_file(test_file_path);
   if (test_file.is_open()) {
@@ -106,7 +104,7 @@ TEST_CASE("Test usage of ExtractText", "[extracttextTest]") {
   LogTestController::getInstance().reset();
   LogTestController::getInstance().setTrace<org::apache::nifi::minifi::processors::LogAttribute>();
 
-  std::ofstream test_file_2(test_file_path + "2");
+  std::ofstream test_file_2(test_file_path.string() + "2");
   if (test_file_2.is_open()) {
     test_file_2 << TEST_TEXT << std::endl;
     test_file_2.close();
@@ -138,7 +136,7 @@ TEST_CASE("Test usage of ExtractText in regex mode", "[extracttextRegexTest]") {
   auto dir = testController.createTempDirectory();
   REQUIRE(!dir.empty());
   std::shared_ptr<core::Processor> getfile = plan->addProcessor("GetFile", "getfileCreate2");
-  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir);
+  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir.string());
   plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::KeepSourceFile.getName(), "true");
 
   std::shared_ptr<core::Processor> maprocessor = plan->addProcessor("ExtractText", "testExtractText", core::Relationship("success", "description"), true);
@@ -150,9 +148,7 @@ TEST_CASE("Test usage of ExtractText in regex mode", "[extracttextRegexTest]") {
   std::shared_ptr<core::Processor> laprocessor = plan->addProcessor("LogAttribute", "outputLogAttribute", core::Relationship("success", "description"), true);
   plan->setProperty(laprocessor, org::apache::nifi::minifi::processors::LogAttribute::AttributesToLog.getName(), TEST_ATTR);
 
-  std::stringstream ss;
-  ss << dir << utils::file::get_separator() << TEST_FILE;
-  std::string test_file_path = ss.str();
+  auto test_file_path = dir / TEST_FILE;
 
   std::ofstream test_file(test_file_path);
   if (test_file.is_open()) {
@@ -209,7 +205,7 @@ TEST_CASE("Test usage of ExtractText in regex mode with large regex matches", "[
   auto dir = test_controller.createTempDirectory();
   REQUIRE(!dir.empty());
   auto getfile = plan->addProcessor("GetFile", "GetFile");
-  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir);
+  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir.string());
   plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::KeepSourceFile.getName(), "true");
 
   auto extract_text_processor = plan->addProcessor("ExtractText", "ExtractText", core::Relationship("success", "description"), true);
diff --git a/extensions/standard-processors/tests/unit/FetchFileTests.cpp b/extensions/standard-processors/tests/unit/FetchFileTests.cpp
index 19deff586..e5315d974 100644
--- a/extensions/standard-processors/tests/unit/FetchFileTests.cpp
+++ b/extensions/standard-processors/tests/unit/FetchFileTests.cpp
@@ -1,3 +1,4 @@
+
 /**
  *
  * Licensed to the Apache Software Foundation (ASF) under one or more
@@ -40,13 +41,13 @@ class FetchFileTestFixture {
   ~FetchFileTestFixture();
 
  protected:
-  [[nodiscard]] std::unordered_multiset<std::string> getDirContents(const std::string& dir_path) const;
+  [[nodiscard]] std::unordered_multiset<std::string> getDirContents(const std::filesystem::path& dir_path) const;
 
   std::shared_ptr<minifi::processors::FetchFile> fetch_file_processor_;
   std::shared_ptr<minifi::test::SingleProcessorTestController> test_controller_;
-  const std::string input_dir_;
-  const std::string permission_denied_file_name_;
-  const std::string input_file_name_;
+  const std::filesystem::path input_dir_;
+  const std::filesystem::path permission_denied_file_name_;
+  const std::filesystem::path input_file_name_;
   const std::string file_content_;
   std::unordered_map<std::string, std::string> attributes_;
 };
@@ -61,22 +62,22 @@ FetchFileTestFixture::FetchFileTestFixture()
   LogTestController::getInstance().setTrace<TestPlan>();
   LogTestController::getInstance().setTrace<minifi::processors::FetchFile>();
 
-  attributes_ = {{"absolute.path", input_dir_}, {"filename", input_file_name_}};
+  attributes_ = {{"absolute.path", input_dir_.string()}, {"filename", input_file_name_.string()}};
 
   utils::putFileToDir(input_dir_, input_file_name_, file_content_);
   utils::putFileToDir(input_dir_, permission_denied_file_name_, file_content_);
-  std::filesystem::permissions(input_dir_ + utils::file::FileUtils::get_separator() + permission_denied_file_name_, static_cast<std::filesystem::perms>(0));
+  std::filesystem::permissions(input_dir_ / permission_denied_file_name_, static_cast<std::filesystem::perms>(0));
 }
 
 FetchFileTestFixture::~FetchFileTestFixture() {
-  std::filesystem::permissions(input_dir_ + utils::file::FileUtils::get_separator() + permission_denied_file_name_, static_cast<std::filesystem::perms>(0644));
+  std::filesystem::permissions(input_dir_ / permission_denied_file_name_, static_cast<std::filesystem::perms>(0644));
 }
 
-std::unordered_multiset<std::string> FetchFileTestFixture::getDirContents(const std::string& dir_path) const {
+std::unordered_multiset<std::string> FetchFileTestFixture::getDirContents(const std::filesystem::path& dir_path) const {
   std::unordered_multiset<std::string> file_contents;
 
-  auto lambda = [&file_contents](const std::string& path, const std::string& filename) -> bool {
-    std::ifstream is(path + utils::file::FileUtils::get_separator() + filename, std::ifstream::binary);
+  auto lambda = [&file_contents](const std::filesystem::path& path, const std::filesystem::path& filename) -> bool {
+    std::ifstream is(path / filename, std::ifstream::binary);
     file_contents.insert(std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>()));
     return true;
   };
@@ -90,7 +91,7 @@ TEST_CASE_METHOD(FetchFileTestFixture, "Test fetching file with default but non-
   const auto result = test_controller_->trigger("", attributes_);
   auto file_contents = result.at(minifi::processors::FetchFile::NotFound);
   REQUIRE(file_contents.size() == 1);
-  REQUIRE(test_controller_->plan->getContent(file_contents[0]) == "");
+  REQUIRE(test_controller_->plan->getContent(file_contents[0]).empty());
   using org::apache::nifi::minifi::utils::verifyLogLinePresenceInPollTime;
   REQUIRE(verifyLogLinePresenceInPollTime(1s, "[error] File to fetch was not found"));
 }
@@ -101,7 +102,7 @@ TEST_CASE_METHOD(FetchFileTestFixture, "FileToFetch property set to a non-existe
   const auto result = test_controller_->trigger("", attributes_);
   auto file_contents = result.at(minifi::processors::FetchFile::NotFound);
   REQUIRE(file_contents.size() == 1);
-  REQUIRE(test_controller_->plan->getContent(file_contents[0]) == "");
+  REQUIRE(test_controller_->plan->getContent(file_contents[0]).empty());
   using org::apache::nifi::minifi::utils::verifyLogLinePresenceInPollTime;
   REQUIRE(verifyLogLinePresenceInPollTime(1s, "[info] File to fetch was not found"));
 }
@@ -109,12 +110,12 @@ TEST_CASE_METHOD(FetchFileTestFixture, "FileToFetch property set to a non-existe
 #ifndef WIN32
 TEST_CASE_METHOD(FetchFileTestFixture, "Permission denied to read file", "[testFetchFile]") {
   fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::FileToFetch,
-    input_dir_ + utils::file::FileUtils::get_separator() + permission_denied_file_name_);
+    input_dir_ / permission_denied_file_name_);
   fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::LogLevelWhenPermissionDenied, "WARN");
   const auto result = test_controller_->trigger("", attributes_);
   auto file_contents = result.at(minifi::processors::FetchFile::PermissionDenied);
   REQUIRE(file_contents.size() == 1);
-  REQUIRE(test_controller_->plan->getContent(file_contents[0]) == "");
+  REQUIRE(test_controller_->plan->getContent(file_contents[0]).empty());
   using org::apache::nifi::minifi::utils::verifyLogLinePresenceInPollTime;
   REQUIRE(verifyLogLinePresenceInPollTime(1s, "[warning] Read permission denied for file"));
 }
@@ -125,14 +126,14 @@ TEST_CASE_METHOD(FetchFileTestFixture, "Test fetching file with default file pat
   auto file_contents = result.at(minifi::processors::FetchFile::Success);
   REQUIRE(file_contents.size() == 1);
   REQUIRE(test_controller_->plan->getContent(file_contents[0]) == file_content_);
-  REQUIRE(utils::file::FileUtils::exists(input_dir_ + utils::file::FileUtils::get_separator() + input_file_name_));
+  REQUIRE(utils::file::FileUtils::exists(input_dir_ / input_file_name_));
 }
 
 TEST_CASE_METHOD(FetchFileTestFixture, "Test fetching file from a custom path", "[testFetchFile]") {
-  REQUIRE(0 == utils::file::FileUtils::create_dir(input_dir_ + utils::file::FileUtils::get_separator() + "sub"));
-  utils::putFileToDir(input_dir_ + utils::file::FileUtils::get_separator() + "sub", input_file_name_, file_content_);
-  auto file_path = input_dir_ + utils::file::FileUtils::get_separator() + "sub" + utils::file::FileUtils::get_separator() + input_file_name_;
-  fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::FileToFetch, file_path);
+  REQUIRE(0 == utils::file::FileUtils::create_dir(input_dir_ / "sub"));
+  utils::putFileToDir(input_dir_ / "sub", input_file_name_, file_content_);
+  auto file_path = input_dir_ / "sub" / input_file_name_;
+  fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::FileToFetch, file_path.string());
   const auto result = test_controller_->trigger("", attributes_);
   auto file_contents = result.at(minifi::processors::FetchFile::Success);
   REQUIRE(file_contents.size() == 1);
@@ -149,22 +150,22 @@ TEST_CASE_METHOD(FetchFileTestFixture, "Flow fails due to move conflict", "[test
   auto move_dir = test_controller_->createTempDirectory();
   utils::putFileToDir(move_dir, input_file_name_, "old content");
   fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::CompletionStrategy, "Move File");
-  fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveDestinationDirectory, move_dir);
+  fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveDestinationDirectory, move_dir.string());
   fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveConflictStrategy, "Fail");
   const auto result = test_controller_->trigger("", attributes_);
   auto file_contents = result.at(minifi::processors::FetchFile::Failure);
   REQUIRE(file_contents.size() == 1);
-  REQUIRE(test_controller_->plan->getContent(file_contents[0]) == "");
+  REQUIRE(test_controller_->plan->getContent(file_contents[0]).empty());
 
-  std::ifstream is(move_dir + utils::file::FileUtils::get_separator() + input_file_name_, std::ifstream::binary);
+  std::ifstream is(move_dir / input_file_name_, std::ifstream::binary);
   REQUIRE(std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>()) == "old content");
-  REQUIRE(utils::file::FileUtils::exists(input_dir_ + utils::file::FileUtils::get_separator() + input_file_name_));
+  REQUIRE(utils::file::FileUtils::exists(input_dir_ / input_file_name_));
 }
 
 TEST_CASE_METHOD(FetchFileTestFixture, "Move specific properties are ignored when completion strategy is not move file", "[testFetchFile]") {
   auto move_dir = test_controller_->createTempDirectory();
   utils::putFileToDir(move_dir, input_file_name_, "old content");
-  fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveDestinationDirectory, move_dir);
+  fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveDestinationDirectory, move_dir.string());
   fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveConflictStrategy, "Fail");
   const auto result = test_controller_->trigger("", attributes_);
   auto file_contents = result.at(minifi::processors::FetchFile::Success);
@@ -176,15 +177,15 @@ TEST_CASE_METHOD(FetchFileTestFixture, "Move destination conflict is resolved wi
   auto move_dir = test_controller_->createTempDirectory();
   utils::putFileToDir(move_dir, input_file_name_, "old content");
   fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::CompletionStrategy, "Move File");
-  fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveDestinationDirectory, move_dir);
+  fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveDestinationDirectory, move_dir.string());
   fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveConflictStrategy, "Replace File");
   const auto result = test_controller_->trigger("", attributes_);
   auto file_contents = result.at(minifi::processors::FetchFile::Success);
   REQUIRE(file_contents.size() == 1);
   REQUIRE(test_controller_->plan->getContent(file_contents[0]) == file_content_);
-  REQUIRE(!utils::file::FileUtils::exists(input_dir_ + utils::file::FileUtils::get_separator() + input_file_name_));
+  REQUIRE(!utils::file::FileUtils::exists(input_dir_ / input_file_name_));
 
-  std::ifstream is(move_dir + utils::file::FileUtils::get_separator() + input_file_name_, std::ifstream::binary);
+  std::ifstream is(move_dir / input_file_name_, std::ifstream::binary);
   REQUIRE(std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>()) == file_content_);
 }
 
@@ -192,13 +193,13 @@ TEST_CASE_METHOD(FetchFileTestFixture, "Move destination conflict is resolved wi
   auto move_dir = test_controller_->createTempDirectory();
   utils::putFileToDir(move_dir, input_file_name_, "old content");
   fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::CompletionStrategy, "Move File");
-  fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveDestinationDirectory, move_dir);
+  fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveDestinationDirectory, move_dir.string());
   fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveConflictStrategy, "Rename");
   const auto result = test_controller_->trigger("", attributes_);
   auto file_contents = result.at(minifi::processors::FetchFile::Success);
   REQUIRE(file_contents.size() == 1);
   REQUIRE(test_controller_->plan->getContent(file_contents[0]) == file_content_);
-  REQUIRE(!utils::file::FileUtils::exists(input_dir_ + utils::file::FileUtils::get_separator() + input_file_name_));
+  REQUIRE(!utils::file::FileUtils::exists(input_dir_ / input_file_name_));
 
   auto move_dir_contents = getDirContents(move_dir);
   std::unordered_multiset<std::string> expected = {"old content", file_content_};
@@ -209,44 +210,44 @@ TEST_CASE_METHOD(FetchFileTestFixture, "Move destination conflict is resolved wi
   auto move_dir = test_controller_->createTempDirectory();
   utils::putFileToDir(move_dir, input_file_name_, "old content");
   fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::CompletionStrategy, "Move File");
-  fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveDestinationDirectory, move_dir);
+  fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveDestinationDirectory, move_dir.string());
   fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveConflictStrategy, "Keep Existing");
   const auto result = test_controller_->trigger("", attributes_);
   auto file_contents = result.at(minifi::processors::FetchFile::Success);
   REQUIRE(file_contents.size() == 1);
   REQUIRE(test_controller_->plan->getContent(file_contents[0]) == file_content_);
-  REQUIRE(!utils::file::FileUtils::exists(input_dir_ + utils::file::FileUtils::get_separator() + input_file_name_));
+  REQUIRE(!utils::file::FileUtils::exists(input_dir_ / input_file_name_));
 
-  std::ifstream is(move_dir + utils::file::FileUtils::get_separator() + input_file_name_, std::ifstream::binary);
+  std::ifstream is(move_dir / input_file_name_, std::ifstream::binary);
   REQUIRE(std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>()) == "old content");
 }
 
 TEST_CASE_METHOD(FetchFileTestFixture, "Fetched file is moved to a new directory after flow completion", "[testFetchFile]") {
   auto move_dir = test_controller_->createTempDirectory();
   fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::CompletionStrategy, "Move File");
-  fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveDestinationDirectory, move_dir);
+  fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveDestinationDirectory, move_dir.string());
   const auto result = test_controller_->trigger("", attributes_);
   auto file_contents = result.at(minifi::processors::FetchFile::Success);
   REQUIRE(file_contents.size() == 1);
   REQUIRE(test_controller_->plan->getContent(file_contents[0]) == file_content_);
-  REQUIRE(!utils::file::FileUtils::exists(input_dir_ + utils::file::FileUtils::get_separator() + input_file_name_));
+  REQUIRE(!utils::file::FileUtils::exists(input_dir_ / input_file_name_));
 
-  std::ifstream is(move_dir + utils::file::FileUtils::get_separator() + input_file_name_, std::ifstream::binary);
+  std::ifstream is(move_dir / input_file_name_, std::ifstream::binary);
   REQUIRE(std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>()) == file_content_);
 }
 
 TEST_CASE_METHOD(FetchFileTestFixture, "After flow completion the fetched file is moved to a non-existent directory which is created by the flow", "[testFetchFile]") {
   auto move_dir = test_controller_->createTempDirectory();
-  move_dir = move_dir + utils::file::FileUtils::get_separator() + "temp";
+  move_dir = move_dir / "temp";
   fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::CompletionStrategy, "Move File");
-  fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveDestinationDirectory, move_dir);
+  fetch_file_processor_->setProperty(org::apache::nifi::minifi::processors::FetchFile::MoveDestinationDirectory, move_dir.string());
   const auto result = test_controller_->trigger("", attributes_);
   auto file_contents = result.at(minifi::processors::FetchFile::Success);
   REQUIRE(file_contents.size() == 1);
   REQUIRE(test_controller_->plan->getContent(file_contents[0]) == file_content_);
-  REQUIRE(!utils::file::FileUtils::exists(input_dir_ + utils::file::FileUtils::get_separator() + input_file_name_));
+  REQUIRE(!utils::file::FileUtils::exists(input_dir_ / input_file_name_));
 
-  std::ifstream is(move_dir + utils::file::FileUtils::get_separator() + input_file_name_, std::ifstream::binary);
+  std::ifstream is(move_dir / input_file_name_, std::ifstream::binary);
   REQUIRE(std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>()) == file_content_);
 }
 
@@ -260,7 +261,7 @@ TEST_CASE_METHOD(FetchFileTestFixture, "Move completion strategy failure due to
   auto file_contents = result.at(minifi::processors::FetchFile::Success);
   REQUIRE(file_contents.size() == 1);
   REQUIRE(test_controller_->plan->getContent(file_contents[0]) == file_content_);
-  REQUIRE(utils::file::FileUtils::exists(input_dir_ + utils::file::FileUtils::get_separator() + input_file_name_));
+  REQUIRE(utils::file::FileUtils::exists(input_dir_ / input_file_name_));
   using org::apache::nifi::minifi::utils::verifyLogLinePresenceInPollTime;
   REQUIRE(verifyLogLinePresenceInPollTime(1s, "completion strategy failed"));
   utils::file::FileUtils::set_permissions(move_dir, 0644);
@@ -273,7 +274,7 @@ TEST_CASE_METHOD(FetchFileTestFixture, "Fetched file is deleted after flow compl
   auto file_contents = result.at(minifi::processors::FetchFile::Success);
   REQUIRE(file_contents.size() == 1);
   REQUIRE(test_controller_->plan->getContent(file_contents[0]) == file_content_);
-  REQUIRE(!utils::file::FileUtils::exists(input_dir_ + utils::file::FileUtils::get_separator() + input_file_name_));
+  REQUIRE(!utils::file::FileUtils::exists(input_dir_ / input_file_name_));
 }
 
 }  // namespace
diff --git a/extensions/standard-processors/tests/unit/GenerateFlowFileTests.cpp b/extensions/standard-processors/tests/unit/GenerateFlowFileTests.cpp
index ededc9da4..e58b32d49 100644
--- a/extensions/standard-processors/tests/unit/GenerateFlowFileTests.cpp
+++ b/extensions/standard-processors/tests/unit/GenerateFlowFileTests.cpp
@@ -41,7 +41,7 @@ TEST_CASE("GenerateFlowFileTest", "[generateflowfiletest]") {
 
   std::shared_ptr<core::Processor> putfile = plan->addProcessor("PutFile", "putfile", core::Relationship("success", "description"), true);
 
-  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), dir);
+  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), dir.string());
 
   plan->setProperty(genfile, org::apache::nifi::minifi::processors::GenerateFlowFile::FileSize.getName(), "10");
   plan->setProperty(genfile, org::apache::nifi::minifi::processors::GenerateFlowFile::BatchSize.getName(), "2");
@@ -57,9 +57,9 @@ TEST_CASE("GenerateFlowFileTest", "[generateflowfiletest]") {
 
   std::vector<std::string> file_contents;
 
-  auto lambda = [&file_contents](const std::string& path, const std::string& filename) -> bool {
-    std::ifstream is(path + utils::file::get_separator() + filename, std::ifstream::binary);
-    file_contents.push_back(std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>()));
+  auto lambda = [&file_contents](const std::filesystem::path& path, const std::filesystem::path& filename) -> bool {
+    std::ifstream is(path / filename, std::ifstream::binary);
+    file_contents.emplace_back(std::istreambuf_iterator<char>(is), std::istreambuf_iterator<char>());
     return true;
   };
 
@@ -85,7 +85,7 @@ TEST_CASE("GenerateFlowFileWithNonUniqueBinaryData", "[generateflowfiletest]") {
 
   std::shared_ptr<core::Processor> putfile = plan->addProcessor("PutFile", "putfile", core::Relationship("success", "description"), true);
 
-  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), dir);
+  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), dir.string());
 
   plan->setProperty(genfile, org::apache::nifi::minifi::processors::GenerateFlowFile::FileSize.getName(), "10");
   plan->setProperty(genfile, org::apache::nifi::minifi::processors::GenerateFlowFile::BatchSize.getName(), "2");
@@ -100,12 +100,12 @@ TEST_CASE("GenerateFlowFileWithNonUniqueBinaryData", "[generateflowfiletest]") {
 
   std::vector<std::vector<char>> fileContents;
 
-  auto lambda = [&fileContents](const std::string& path, const std::string& filename) -> bool {
-    std::ifstream is(path + utils::file::get_separator() + filename, std::ifstream::binary);
+  auto lambda = [&fileContents](const std::filesystem::path& path, const std::filesystem::path& filename) -> bool {
+    std::ifstream is(path / filename, std::ifstream::binary);
 
-    is.seekg(0, is.end);
+    is.seekg(0, std::ifstream::end);
     auto length = gsl::narrow<size_t>(is.tellg());
-    is.seekg(0, is.beg);
+    is.seekg(0, std::ifstream::beg);
 
     std::vector<char> content(length);
 
@@ -137,7 +137,7 @@ TEST_CASE("GenerateFlowFileTestEmpty", "[generateemptyfiletest]") {
 
   std::shared_ptr<core::Processor> putfile = plan->addProcessor("PutFile", "putfile", core::Relationship("success", "description"), true);
 
-  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), dir);
+  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), dir.string());
 
   plan->setProperty(genfile, org::apache::nifi::minifi::processors::GenerateFlowFile::FileSize.getName(), "0");
 
@@ -146,10 +146,10 @@ TEST_CASE("GenerateFlowFileTestEmpty", "[generateemptyfiletest]") {
 
   size_t counter = 0;
 
-  auto lambda = [&counter](const std::string& path, const std::string& filename) -> bool {
-    std::ifstream is(path + utils::file::get_separator() + filename, std::ifstream::binary);
+  auto lambda = [&counter](const std::filesystem::path& path, const std::filesystem::path& filename) -> bool {
+    std::ifstream is(path / filename, std::ifstream::binary);
 
-    is.seekg(0, is.end);
+    is.seekg(0, std::ifstream::end);
     REQUIRE(is.tellg() == 0);
 
     counter++;
@@ -174,7 +174,7 @@ TEST_CASE("GenerateFlowFileCustomTextTest", "[generateflowfiletest]") {
 
   std::shared_ptr<core::Processor> putfile = plan->addProcessor("PutFile", "putfile", core::Relationship("success", "description"), true);
 
-  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), dir);
+  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), dir.string());
 
   plan->setProperty(genfile, org::apache::nifi::minifi::processors::GenerateFlowFile::CustomText.getName(), "${UUID()}");
   plan->setProperty(genfile, org::apache::nifi::minifi::processors::GenerateFlowFile::UniqueFlowFiles.getName(), "false");
@@ -186,9 +186,9 @@ TEST_CASE("GenerateFlowFileCustomTextTest", "[generateflowfiletest]") {
 
   std::vector<std::string> file_contents;
 
-  auto lambda = [&file_contents](const std::string& path, const std::string& filename) -> bool {
-    std::ifstream is(path + utils::file::get_separator() + filename, std::ifstream::binary);
-    file_contents.push_back(std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>()));
+  auto lambda = [&file_contents](const std::filesystem::path& path, const std::filesystem::path& filename) -> bool {
+    std::ifstream is(path / filename, std::ifstream::binary);
+    file_contents.emplace_back(std::istreambuf_iterator<char>(is), std::istreambuf_iterator<char>());
     return true;
   };
 
@@ -210,7 +210,7 @@ TEST_CASE("GenerateFlowFileCustomTextEmptyTest", "[generateflowfiletest]") {
 
   std::shared_ptr<core::Processor> putfile = plan->addProcessor("PutFile", "putfile", core::Relationship("success", "description"), true);
 
-  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), dir);
+  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), dir.string());
 
   plan->setProperty(genfile, org::apache::nifi::minifi::processors::GenerateFlowFile::FileSize.getName(), "10");
   plan->setProperty(genfile, org::apache::nifi::minifi::processors::GenerateFlowFile::CustomText.getName(), "");
@@ -223,9 +223,9 @@ TEST_CASE("GenerateFlowFileCustomTextEmptyTest", "[generateflowfiletest]") {
 
   std::vector<std::string> file_contents;
 
-  auto lambda = [&file_contents](const std::string& path, const std::string& filename) -> bool {
-    std::ifstream is(path + utils::file::get_separator() + filename, std::ifstream::binary);
-    file_contents.push_back(std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>()));
+  auto lambda = [&file_contents](const std::filesystem::path& path, const std::filesystem::path& filename) -> bool {
+    std::ifstream is(path / filename, std::ifstream::binary);
+    file_contents.emplace_back(std::istreambuf_iterator<char>(is), std::istreambuf_iterator<char>());
     return true;
   };
 
diff --git a/extensions/standard-processors/tests/unit/GetFileTests.cpp b/extensions/standard-processors/tests/unit/GetFileTests.cpp
index d454bca08..ec8979c1a 100644
--- a/extensions/standard-processors/tests/unit/GetFileTests.cpp
+++ b/extensions/standard-processors/tests/unit/GetFileTests.cpp
@@ -19,6 +19,7 @@
 #include <memory>
 #include <string>
 #include <fstream>
+#include <filesystem>
 #include <chrono>
 
 #include "TestBase.h"
@@ -28,6 +29,7 @@
 #include "utils/file/FileUtils.h"
 #include "utils/TestUtils.h"
 #include "unit/ProvenanceTestHelper.h"
+#include "Utils.h"
 
 #ifdef WIN32
 #include <fileapi.h>
@@ -40,8 +42,8 @@ namespace {
 class GetFileTestController {
  public:
   GetFileTestController();
-  [[nodiscard]] std::string getFullPath(const std::string& filename) const;
-  [[nodiscard]] std::string getInputFilePath() const;
+  [[nodiscard]] std::filesystem::path getFullPath(const std::filesystem::path& filename) const;
+  [[nodiscard]] std::filesystem::path getInputFilePath() const;
   void setProperty(const core::Property& property, const std::string& value);
   void runSession();
   void resetTestPlan();
@@ -49,10 +51,10 @@ class GetFileTestController {
  private:
   TestController test_controller_;
   std::shared_ptr<TestPlan> test_plan_;
-  std::string temp_dir_;
-  std::string input_file_name_;
-  std::string large_input_file_name_;
-  std::string hidden_input_file_name_;
+  std::filesystem::path temp_dir_;
+  std::filesystem::path input_file_name_;
+  std::filesystem::path large_input_file_name_;
+  std::filesystem::path hidden_input_file_name_;
   std::shared_ptr<core::Processor> get_file_processor_;
 };
 
@@ -70,25 +72,25 @@ GetFileTestController::GetFileTestController()
 
   // Build MiNiFi processing graph
   get_file_processor_ = test_plan_->addProcessor("GetFile", "Get");
-  test_plan_->setProperty(get_file_processor_, minifi::processors::GetFile::Directory.getName(), temp_dir_);
+  test_plan_->setProperty(get_file_processor_, minifi::processors::GetFile::Directory.getName(), temp_dir_.string());
   auto log_attr = test_plan_->addProcessor("LogAttribute", "Log", core::Relationship("success", "description"), true);
   test_plan_->setProperty(log_attr, minifi::processors::LogAttribute::FlowFilesToLog.getName(), "0");
 
-  utils::putFileToDir(temp_dir_, input_file_name_, "The quick brown fox jumps over the lazy dog\n");
-  utils::putFileToDir(temp_dir_, large_input_file_name_, "The quick brown fox jumps over the lazy dog who is 2 legit to quit\n");
-  utils::putFileToDir(temp_dir_, hidden_input_file_name_, "But noone has ever seen it\n");
+  utils::putFileToDir(temp_dir_.string(), input_file_name_, "The quick brown fox jumps over the lazy dog\n");
+  utils::putFileToDir(temp_dir_.string(), large_input_file_name_, "The quick brown fox jumps over the lazy dog who is 2 legit to quit\n");
+  utils::putFileToDir(temp_dir_.string(), hidden_input_file_name_, "But noone has ever seen it\n");
 
 #ifdef WIN32
-  const auto hide_file_err = utils::file::FileUtils::hide_file(getFullPath(hidden_input_file_name_).c_str());
+  const auto hide_file_err = minifi::test::utils::hide_file(getFullPath(hidden_input_file_name_).c_str());
   REQUIRE(!hide_file_err);
 #endif
 }
 
-std::string GetFileTestController::getFullPath(const std::string& filename) const {
-  return temp_dir_ + utils::file::FileUtils::get_separator() + filename;
+std::filesystem::path GetFileTestController::getFullPath(const std::filesystem::path& filename) const {
+  return temp_dir_ / filename;
 }
 
-std::string GetFileTestController::getInputFilePath() const {
+std::filesystem::path GetFileTestController::getInputFilePath() const {
   return getFullPath(input_file_name_);
 }
 
diff --git a/extensions/standard-processors/tests/unit/HashContentTest.cpp b/extensions/standard-processors/tests/unit/HashContentTest.cpp
index 5070c3b7a..d9408a9b5 100644
--- a/extensions/standard-processors/tests/unit/HashContentTest.cpp
+++ b/extensions/standard-processors/tests/unit/HashContentTest.cpp
@@ -75,7 +75,7 @@ TEST_CASE("Test usage of ExtractText", "[extracttextTest]") {
   REQUIRE(!tempdir.empty());
 
   std::shared_ptr<core::Processor> getfile = plan->addProcessor("GetFile", "getfileCreate2");
-  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), tempdir);
+  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), tempdir.string());
   plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::KeepSourceFile.getName(), "true");
 
   std::shared_ptr<core::Processor> md5processor = plan->addProcessor("HashContent", "HashContentMD5",
@@ -96,9 +96,7 @@ TEST_CASE("Test usage of ExtractText", "[extracttextTest]") {
   std::shared_ptr<core::Processor> laprocessor = plan->addProcessor("LogAttribute", "outputLogAttribute",
       core::Relationship("success", "description"), true);
 
-  std::stringstream ss1;
-  ss1 << tempdir << utils::file::get_separator() << TEST_FILE;
-  std::string test_file_path = ss1.str();
+  auto test_file_path = tempdir / TEST_FILE;
 
   std::ofstream test_file(test_file_path, std::ios::binary);
 
@@ -144,7 +142,7 @@ TEST_CASE("TestingFailOnEmptyProperty", "[HashContentPropertiesCheck]") {
 
   auto tempdir = testController.createTempDirectory();
   std::shared_ptr<core::Processor> getfile = plan->addProcessor("GetFile", "getfileCreate2");
-  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), tempdir);
+  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), tempdir.string());
   plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::KeepSourceFile.getName(), "true");
 
   std::shared_ptr<core::Processor> md5processor = plan->addProcessor("HashContent", "HashContentMD5",
@@ -154,9 +152,7 @@ TEST_CASE("TestingFailOnEmptyProperty", "[HashContentPropertiesCheck]") {
 
   md5processor->setAutoTerminatedRelationships(std::array{HashContent::Success, HashContent::Failure});
 
-  std::stringstream stream_dir;
-  stream_dir << tempdir << utils::file::get_separator() << TEST_FILE;
-  std::string test_file_path = stream_dir.str();
+  auto test_file_path = tempdir / TEST_FILE;
   std::ofstream test_file(test_file_path, std::ios::binary);
 
   SECTION("with an empty file and fail on empty property set to false") {
diff --git a/extensions/standard-processors/tests/unit/ListFileTests.cpp b/extensions/standard-processors/tests/unit/ListFileTests.cpp
index cfef64bbf..7e52ccf14 100644
--- a/extensions/standard-processors/tests/unit/ListFileTests.cpp
+++ b/extensions/standard-processors/tests/unit/ListFileTests.cpp
@@ -27,11 +27,22 @@
 #include "utils/TestUtils.h"
 #include "utils/IntegrationTestUtils.h"
 #include "utils/file/PathUtils.h"
+#include "Utils.h"
 
 using namespace std::literals::chrono_literals;
 
 namespace {
 
+#ifdef WIN32
+inline char get_separator() {
+  return '\\';
+}
+#else
+inline char get_separator() {
+  return '/';
+}
+#endif
+
 using org::apache::nifi::minifi::utils::verifyLogLinePresenceInPollTime;
 
 class ListFileTestFixture {
@@ -42,13 +53,13 @@ class ListFileTestFixture {
  protected:
   TestController test_controller_;
   std::shared_ptr<TestPlan> plan_;
-  const std::string input_dir_;
+  const std::filesystem::path input_dir_;
   std::shared_ptr<core::Processor> list_file_processor_;
-  std::string hidden_file_path_;
-  std::string empty_file_abs_path_;
-  std::string standard_file_abs_path_;
-  std::string first_sub_file_abs_path_;
-  std::string second_sub_file_abs_path_;
+  std::filesystem::path hidden_file_path_;
+  std::filesystem::path empty_file_abs_path_;
+  std::filesystem::path standard_file_abs_path_;
+  std::filesystem::path first_sub_file_abs_path_;
+  std::filesystem::path second_sub_file_abs_path_;
 };
 
 const std::string ListFileTestFixture::FORMAT_STRING = "%Y-%m-%dT%H:%M:%SZ";
@@ -63,29 +74,29 @@ ListFileTestFixture::ListFileTestFixture()
   REQUIRE(!input_dir_.empty());
 
   list_file_processor_ = plan_->addProcessor("ListFile", "ListFile");
-  plan_->setProperty(list_file_processor_, "Input Directory", input_dir_);
+  plan_->setProperty(list_file_processor_, "Input Directory", input_dir_.string());
   auto log_attribute = plan_->addProcessor("LogAttribute", "logAttribute", core::Relationship("success", "description"), true);
   plan_->setProperty(log_attribute, "FlowFiles To Log", "0");
 
   hidden_file_path_ = utils::putFileToDir(input_dir_, ".hidden_file.txt", "hidden");
   standard_file_abs_path_ = utils::putFileToDir(input_dir_, "standard_file.log", "test");
   empty_file_abs_path_ = utils::putFileToDir(input_dir_, "empty_file.txt", "");
-  utils::file::FileUtils::create_dir(input_dir_ + utils::file::FileUtils::get_separator() + "first_subdir");
-  first_sub_file_abs_path_ = utils::putFileToDir(input_dir_ + utils::file::FileUtils::get_separator() + "first_subdir", "sub_file_one.txt", "the");
-  utils::file::FileUtils::create_dir(input_dir_ + utils::file::FileUtils::get_separator() + "second_subdir");
-  second_sub_file_abs_path_ = utils::putFileToDir(input_dir_ + utils::file::FileUtils::get_separator() + "second_subdir", "sub_file_two.txt", "some_other_content");
+  utils::file::FileUtils::create_dir(input_dir_ / "first_subdir");
+  first_sub_file_abs_path_ = utils::putFileToDir(input_dir_ / "first_subdir", "sub_file_one.txt", "the");
+  utils::file::FileUtils::create_dir(input_dir_ / "second_subdir");
+  second_sub_file_abs_path_ = utils::putFileToDir(input_dir_ / "second_subdir", "sub_file_two.txt", "some_other_content");
 
   auto last_write_time = *utils::file::FileUtils::last_write_time(standard_file_abs_path_);
   utils::file::FileUtils::set_last_write_time(empty_file_abs_path_, last_write_time - 1h);
   utils::file::FileUtils::set_last_write_time(first_sub_file_abs_path_, last_write_time - 2h);
   utils::file::FileUtils::set_last_write_time(second_sub_file_abs_path_, last_write_time - 3h);
 #ifndef WIN32
-  REQUIRE(0 == utils::file::FileUtils::set_permissions(input_dir_ + utils::file::FileUtils::get_separator() + "empty_file.txt", 0755));
-  REQUIRE(0 == utils::file::FileUtils::set_permissions(input_dir_ + utils::file::FileUtils::get_separator() + "standard_file.log", 0644));
+  REQUIRE(0 == utils::file::FileUtils::set_permissions(input_dir_ / "empty_file.txt", 0755));
+  REQUIRE(0 == utils::file::FileUtils::set_permissions(input_dir_ / "standard_file.log", 0644));
 #endif
 
 #ifdef WIN32
-  const auto hide_file_error = utils::file::FileUtils::hide_file(hidden_file_path_.c_str());
+  const auto hide_file_error = minifi::test::utils::hide_file(hidden_file_path_.c_str());
   REQUIRE(!hide_file_error);
 #endif
 }
@@ -101,19 +112,15 @@ TEST_CASE_METHOD(ListFileTestFixture, "Test listing files only once with default
   REQUIRE(verifyLogLinePresenceInPollTime(3s, "key:filename value:empty_file.txt"));
   REQUIRE(verifyLogLinePresenceInPollTime(3s, "key:filename value:sub_file_one.txt"));
   REQUIRE(verifyLogLinePresenceInPollTime(3s, "key:filename value:sub_file_two.txt"));
-  std::string file_path;
-  std::string file_name;
-  utils::file::getFileNameAndPath(empty_file_abs_path_, file_path, file_name);
-  REQUIRE(verifyLogLinePresenceInPollTime(3s, "key:absolute.path value:" + file_path + utils::file::FileUtils::get_separator() + "\n"));
-  utils::file::getFileNameAndPath(standard_file_abs_path_, file_path, file_name);
-  REQUIRE(verifyLogLinePresenceInPollTime(3s, "key:absolute.path value:" + file_path + utils::file::FileUtils::get_separator() + "\n"));
-  utils::file::getFileNameAndPath(first_sub_file_abs_path_, file_path, file_name);
-  REQUIRE(verifyLogLinePresenceInPollTime(3s, "key:absolute.path value:" + file_path + utils::file::FileUtils::get_separator() + "\n"));
-  utils::file::getFileNameAndPath(second_sub_file_abs_path_, file_path, file_name);
-  REQUIRE(verifyLogLinePresenceInPollTime(3s, "key:absolute.path value:" + file_path + utils::file::FileUtils::get_separator() + "\n"));
-  REQUIRE(LogTestController::getInstance().countOccurrences(std::string("key:path value:.") + utils::file::FileUtils::get_separator() + "\n") == 2);
-  REQUIRE(verifyLogLinePresenceInPollTime(3s, std::string("key:path value:first_subdir") + utils::file::FileUtils::get_separator()));
-  REQUIRE(verifyLogLinePresenceInPollTime(3s, std::string("key:path value:second_subdir") + utils::file::FileUtils::get_separator()));
+
+  REQUIRE(verifyLogLinePresenceInPollTime(3s, "key:absolute.path value:" + (empty_file_abs_path_.parent_path() / "").string() + "\n"));
+  REQUIRE(verifyLogLinePresenceInPollTime(3s, "key:absolute.path value:" + (standard_file_abs_path_.parent_path() / "").string() + "\n"));
+  REQUIRE(verifyLogLinePresenceInPollTime(3s, "key:absolute.path value:" + (first_sub_file_abs_path_.parent_path() / "").string() + "\n"));
+  REQUIRE(verifyLogLinePresenceInPollTime(3s, "key:absolute.path value:" + (second_sub_file_abs_path_.parent_path() / "").string() + "\n"));
+
+  REQUIRE(LogTestController::getInstance().countOccurrences(std::string("key:path value:.") + get_separator() + "\n") == 2);
+  REQUIRE(verifyLogLinePresenceInPollTime(3s, std::string("key:path value:first_subdir") + get_separator()));
+  REQUIRE(verifyLogLinePresenceInPollTime(3s, std::string("key:path value:second_subdir") + get_separator()));
   REQUIRE(LogTestController::getInstance().countOccurrences("key:filename value:.hidden_file.txt") == 0);
   REQUIRE(verifyLogLinePresenceInPollTime(3s, "key:file.size value:0"));
   REQUIRE(verifyLogLinePresenceInPollTime(3s, "key:file.size value:4"));
diff --git a/extensions/standard-processors/tests/unit/ListenSyslogTests.cpp b/extensions/standard-processors/tests/unit/ListenSyslogTests.cpp
index 3c98aec6e..9a4aa65a5 100644
--- a/extensions/standard-processors/tests/unit/ListenSyslogTests.cpp
+++ b/extensions/standard-processors/tests/unit/ListenSyslogTests.cpp
@@ -494,12 +494,9 @@ TEST_CASE("Test ListenSyslog via TCP with SSL connection", "[ListenSyslog][Netwo
   SingleProcessorTestController controller{listen_syslog};
   auto ssl_context_service = controller.plan->addController("SSLContextService", "SSLContextService");
   const auto executable_dir = minifi::utils::file::FileUtils::get_executable_dir();
-  REQUIRE(controller.plan->setProperty(ssl_context_service, controllers::SSLContextService::CACertificate.getName(),
-      minifi::utils::file::concat_path(executable_dir, "resources/ca_A.crt")));
-  REQUIRE(controller.plan->setProperty(ssl_context_service, controllers::SSLContextService::ClientCertificate.getName(),
-      minifi::utils::file::concat_path(executable_dir, "resources/localhost_by_A.pem")));
-  REQUIRE(controller.plan->setProperty(ssl_context_service, controllers::SSLContextService::PrivateKey.getName(),
-      minifi::utils::file::concat_path(executable_dir, "resources/localhost_by_A.pem")));
+  REQUIRE(controller.plan->setProperty(ssl_context_service, controllers::SSLContextService::CACertificate.getName(), (executable_dir / "resources" / "ca_A.crt").string()));
+  REQUIRE(controller.plan->setProperty(ssl_context_service, controllers::SSLContextService::ClientCertificate.getName(), (executable_dir / "resources" / "localhost_by_A.pem").string()));
+  REQUIRE(controller.plan->setProperty(ssl_context_service, controllers::SSLContextService::PrivateKey.getName(), (executable_dir / "resources" / "localhost_by_A.pem").string()));
   LogTestController::getInstance().setTrace<ListenSyslog>();
   REQUIRE(listen_syslog->setProperty(ListenSyslog::Port, std::to_string(SYSLOG_PORT)));
   REQUIRE(listen_syslog->setProperty(ListenSyslog::MaxBatchSize, "2"));
@@ -508,8 +505,8 @@ TEST_CASE("Test ListenSyslog via TCP with SSL connection", "[ListenSyslog][Netwo
   REQUIRE(listen_syslog->setProperty(ListenSyslog::SSLContextService, "SSLContextService"));
   ssl_context_service->enable();
   controller.plan->scheduleProcessor(listen_syslog);
-  REQUIRE(utils::sendMessagesViaSSL({rfc5424_logger_example_1}, endpoint, minifi::utils::file::concat_path(executable_dir, "resources/ca_A.crt")));
-  REQUIRE(utils::sendMessagesViaSSL({invalid_syslog}, endpoint, minifi::utils::file::concat_path(executable_dir, "/resources/ca_A.crt")));
+  REQUIRE(utils::sendMessagesViaSSL({rfc5424_logger_example_1}, endpoint, executable_dir / "resources" / "ca_A.crt"));
+  REQUIRE(utils::sendMessagesViaSSL({invalid_syslog}, endpoint, executable_dir / "resources" / "ca_A.crt"));
 
   std::unordered_map<core::Relationship, std::vector<std::shared_ptr<core::FlowFile>>> result;
   REQUIRE(controller.triggerUntil({{ListenSyslog::Success, 2}}, result, 300ms, 50ms));
diff --git a/extensions/standard-processors/tests/unit/ListenTcpTests.cpp b/extensions/standard-processors/tests/unit/ListenTcpTests.cpp
index 9009383b5..158e1a072 100644
--- a/extensions/standard-processors/tests/unit/ListenTcpTests.cpp
+++ b/extensions/standard-processors/tests/unit/ListenTcpTests.cpp
@@ -118,12 +118,9 @@ TEST_CASE("Test ListenTCP with SSL connection", "[ListenTCP][NetworkListenerProc
   auto ssl_context_service = controller.plan->addController("SSLContextService", "SSLContextService");
   LogTestController::getInstance().setTrace<ListenTCP>();
   const auto executable_dir = minifi::utils::file::FileUtils::get_executable_dir();
-  REQUIRE(controller.plan->setProperty(ssl_context_service, controllers::SSLContextService::CACertificate.getName(),
-      minifi::utils::file::concat_path(executable_dir, "resources/ca_A.crt")));
-  REQUIRE(controller.plan->setProperty(ssl_context_service, controllers::SSLContextService::ClientCertificate.getName(),
-      minifi::utils::file::concat_path(executable_dir, "resources/localhost_by_A.pem")));
-  REQUIRE(controller.plan->setProperty(ssl_context_service, controllers::SSLContextService::PrivateKey.getName(),
-      minifi::utils::file::concat_path(executable_dir, "resources/localhost_by_A.pem")));
+  REQUIRE(controller.plan->setProperty(ssl_context_service, controllers::SSLContextService::CACertificate.getName(), (executable_dir / "resources" / "ca_A.crt").string()));
+  REQUIRE(controller.plan->setProperty(ssl_context_service, controllers::SSLContextService::ClientCertificate.getName(), (executable_dir / "resources" / "localhost_by_A.pem").string()));
+  REQUIRE(controller.plan->setProperty(ssl_context_service, controllers::SSLContextService::PrivateKey.getName(), (executable_dir / "resources" / "localhost_by_A.pem").string()));
   REQUIRE(controller.plan->setProperty(ssl_context_service, controllers::SSLContextService::Passphrase.getName(), "Password12"));
   REQUIRE(controller.plan->setProperty(listen_tcp, ListenTCP::Port.getName(), std::to_string(PORT)));
   REQUIRE(controller.plan->setProperty(listen_tcp, ListenTCP::MaxBatchSize.getName(), "2"));
@@ -158,8 +155,8 @@ TEST_CASE("Test ListenTCP with SSL connection", "[ListenTCP][NetworkListenerProc
     controller.plan->scheduleProcessor(listen_tcp);
 
     expected_successful_messages = {"test_message_1", "another_message"};
-    for (const auto& message: expected_successful_messages) {
-      REQUIRE(utils::sendMessagesViaSSL({message}, endpoint, minifi::utils::file::concat_path(executable_dir, "resources/ca_A.crt")));
+    for (const auto& message : expected_successful_messages) {
+      REQUIRE(utils::sendMessagesViaSSL({message}, endpoint, executable_dir / "resources" / "ca_A.crt"));
     }
   }
 
@@ -190,14 +187,14 @@ TEST_CASE("Test ListenTCP with SSL connection", "[ListenTCP][NetworkListenerProc
     controller.plan->scheduleProcessor(listen_tcp);
 
     minifi::utils::net::SslData ssl_data;
-    ssl_data.ca_loc = minifi::utils::file::FileUtils::get_executable_dir() + "/resources/ca_A.crt";
-    ssl_data.cert_loc = minifi::utils::file::FileUtils::get_executable_dir() + "/resources/localhost_by_A.pem";
-    ssl_data.key_loc = minifi::utils::file::FileUtils::get_executable_dir() + "/resources/localhost_by_A.pem";
+    ssl_data.ca_loc = executable_dir / "resources" / "ca_A.crt";
+    ssl_data.cert_loc = executable_dir / "resources" / "localhost_by_A.pem";
+    ssl_data.key_loc = executable_dir / "resources" / "localhost_by_A.pem";
     ssl_data.key_pw = "Password12";
 
     expected_successful_messages = {"test_message_1", "another_message"};
     for (const auto& message : expected_successful_messages) {
-      REQUIRE(utils::sendMessagesViaSSL({message}, endpoint, minifi::utils::file::FileUtils::get_executable_dir() + "/resources/ca_A.crt", ssl_data));
+      REQUIRE(utils::sendMessagesViaSSL({message}, endpoint, executable_dir / "resources" / "ca_A.crt", ssl_data));
     }
   }
 
@@ -214,11 +211,11 @@ TEST_CASE("Test ListenTCP with SSL connection", "[ListenTCP][NetworkListenerProc
     ssl_context_service->enable();
     controller.plan->scheduleProcessor(listen_tcp);
 
-    REQUIRE_FALSE(utils::sendMessagesViaSSL({"test_message_1"}, endpoint, minifi::utils::file::concat_path(executable_dir, "/resources/ca_A.crt")));
+    REQUIRE_FALSE(utils::sendMessagesViaSSL({"test_message_1"}, endpoint, executable_dir / "resources" / "ca_A.crt"));
   }
 
   ProcessorTriggerResult result;
-  REQUIRE(controller.triggerUntil({{ListenTCP::Success, expected_successful_messages.size()}}, result, 300s, 50ms));
+  REQUIRE(controller.triggerUntil({{ListenTCP::Success, expected_successful_messages.size()}}, result, 300ms, 50ms));
   for (std::size_t i = 0; i < expected_successful_messages.size(); ++i) {
     CHECK(controller.plan->getContent(result.at(ListenTCP::Success)[i]) == expected_successful_messages[i]);
     check_for_attributes(*result.at(ListenTCP::Success)[i]);
diff --git a/extensions/standard-processors/tests/unit/ProcessorTests.cpp b/extensions/standard-processors/tests/unit/ProcessorTests.cpp
index e7253779b..d8d427316 100644
--- a/extensions/standard-processors/tests/unit/ProcessorTests.cpp
+++ b/extensions/standard-processors/tests/unit/ProcessorTests.cpp
@@ -90,7 +90,7 @@ TEST_CASE("Test GetFileMultiple", "[getfileCreate3]") {
   auto node = std::make_shared<core::ProcessorNode>(processor.get());
   auto context = std::make_shared<core::ProcessContext>(node, nullptr, repo, repo, content_repo);
 
-  context->setProperty(org::apache::nifi::minifi::processors::GetFile::Directory, dir);
+  context->setProperty(org::apache::nifi::minifi::processors::GetFile::Directory, dir.string());
   // replicate 10 threads
   processor->setScheduledState(core::ScheduledState::RUNNING);
 
@@ -113,16 +113,15 @@ TEST_CASE("Test GetFileMultiple", "[getfileCreate3]") {
     REQUIRE(records.empty());
 
     std::fstream file;
-    std::stringstream ss;
-    ss << dir << utils::file::FileUtils::get_separator() << "tstFile.ext";
-    file.open(ss.str(), std::ios::out);
+    auto path = dir / "tstFile.ext";
+    file.open(path, std::ios::out);
     file << "tempFile";
     file.close();
 
     processor->incrementActiveTasks();
     processor->setScheduledState(core::ScheduledState::RUNNING);
     processor->onTrigger(context, session);
-    std::remove(ss.str().c_str());
+    std::filesystem::remove(path);
     reporter = session->getProvenanceReporter();
 
     REQUIRE(processor->getName() == "getfileCreate2");
@@ -174,7 +173,7 @@ TEST_CASE("Test GetFile Ignore", "[getfileCreate3]") {
   auto node = std::make_shared<core::ProcessorNode>(processor.get());
   auto context = std::make_shared<core::ProcessContext>(node, nullptr, repo, repo, content_repo);
 
-  context->setProperty(org::apache::nifi::minifi::processors::GetFile::Directory, dir);
+  context->setProperty(org::apache::nifi::minifi::processors::GetFile::Directory, dir.string());
   // replicate 10 threads
   processor->setScheduledState(core::ScheduledState::RUNNING);
 
@@ -197,11 +196,7 @@ TEST_CASE("Test GetFile Ignore", "[getfileCreate3]") {
   REQUIRE(record == nullptr);
   REQUIRE(records.empty());
 
-  const std::string hidden_file_name = [&] {
-    std::stringstream ss;
-    ss << dir << utils::file::FileUtils::get_separator() << ".filewithoutanext";
-    return ss.str();
-  }();
+  const std::filesystem::path hidden_file_name = dir / ".filewithoutanext";
   {
     std::ofstream file{ hidden_file_name };
     file << "tempFile";
@@ -210,7 +205,7 @@ TEST_CASE("Test GetFile Ignore", "[getfileCreate3]") {
 #ifdef WIN32
   {
     // hide file on windows, because a . prefix in the filename doesn't imply a hidden file
-    const auto hide_file_error = utils::file::FileUtils::hide_file(hidden_file_name.c_str());
+    const auto hide_file_error = minifi::test::utils::hide_file(hidden_file_name.c_str());
     REQUIRE(!hide_file_error);
   }
 #endif /* WIN32 */
@@ -218,7 +213,7 @@ TEST_CASE("Test GetFile Ignore", "[getfileCreate3]") {
   processor->incrementActiveTasks();
   processor->setScheduledState(core::ScheduledState::RUNNING);
   processor->onTrigger(context, session);
-  std::remove(hidden_file_name.c_str());
+  std::filesystem::remove(hidden_file_name);
   reporter = session->getProvenanceReporter();
 
   REQUIRE(processor->getName() == "getfileCreate2");
@@ -299,7 +294,7 @@ TEST_CASE("LogAttributeTest", "[getfileCreate3]") {
 
   auto dir = testController.createTempDirectory();
 
-  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir);
+  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir.string());
   testController.runSession(plan, false);
   auto records = plan->getProvenanceRecords();
   std::shared_ptr<core::FlowFile> record = plan->getCurrentFlowFile();
@@ -307,15 +302,14 @@ TEST_CASE("LogAttributeTest", "[getfileCreate3]") {
   REQUIRE(records.empty());
 
   std::fstream file;
-  std::stringstream ss;
-  ss << dir << utils::file::FileUtils::get_separator() << "tstFile.ext";
-  file.open(ss.str(), std::ios::out);
+  auto path = dir / "tstFile.ext";
+  file.open(path, std::ios::out);
   file << "tempFile";
   file.close();
   plan->reset();
   testController.runSession(plan, false);
 
-  std::remove(ss.str().c_str());
+  std::filesystem::remove(path);
 
   records = plan->getProvenanceRecords();
   record = plan->getCurrentFlowFile();
@@ -324,9 +318,9 @@ TEST_CASE("LogAttributeTest", "[getfileCreate3]") {
   records = plan->getProvenanceRecords();
   record = plan->getCurrentFlowFile();
 
-  REQUIRE(true == LogTestController::getInstance().contains("key:absolute.path value:" + ss.str()));
+  REQUIRE(true == LogTestController::getInstance().contains("key:absolute.path value:" + path.string()));
   REQUIRE(true == LogTestController::getInstance().contains("Size:8 Offset:0"));
-  REQUIRE(true == LogTestController::getInstance().contains("key:path value:" + std::string(dir)));
+  REQUIRE(true == LogTestController::getInstance().contains("key:path value:" + dir.string()));
   LogTestController::getInstance().reset();
 }
 
@@ -342,7 +336,7 @@ TEST_CASE("LogAttributeTestInvalid", "[TestLogAttribute]") {
 
   auto dir = testController.createTempDirectory();
 
-  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir);
+  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir.string());
   plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::BatchSize.getName(), "1");
   REQUIRE_THROWS_AS(plan->setProperty(loggattr, org::apache::nifi::minifi::processors::LogAttribute::FlowFilesToLog.getName(), "-1"), utils::internal::ParseException);
   LogTestController::getInstance().reset();
@@ -363,7 +357,7 @@ void testMultiplesLogAttribute(int fileCount, int flowsToLog, std::string verify
   auto flowsToLogStr = std::to_string(flowsToLog);
   if (verifyStringFlowsLogged.empty())
     verifyStringFlowsLogged = std::to_string(flowsToLog);
-  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir);
+  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir.string());
   plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::BatchSize.getName(), std::to_string(fileCount));
   plan->setProperty(loggattr, org::apache::nifi::minifi::processors::LogAttribute::FlowFilesToLog.getName(), flowsToLogStr);
   testController.runSession(plan, false);
@@ -372,24 +366,23 @@ void testMultiplesLogAttribute(int fileCount, int flowsToLog, std::string verify
   REQUIRE(record == nullptr);
   REQUIRE(records.empty());
 
-  std::vector<std::string> files;
+  std::vector<std::filesystem::path> files;
 
   for (int i = 0; i < fileCount; i++) {
     std::fstream file;
-    std::stringstream ss;
-    ss << dir << utils::file::FileUtils::get_separator() << "tstFile" << i << ".ext";
-    file.open(ss.str(), std::ios::out);
+    auto path = dir / ("tstFile" + std::to_string(i) + ".ext");
+    file.open(path, std::ios::out);
     file << "tempFile";
     file.close();
 
-    files.push_back(ss.str());
+    files.push_back(path);
   }
 
   plan->reset();
   testController.runSession(plan, false);
 
-  for (const auto &created_file : files) {
-    std::remove(created_file.c_str());
+  for (const auto& created_file : files) {
+    std::filesystem::remove(created_file);
   }
 
   records = plan->getProvenanceRecords();
@@ -400,7 +393,7 @@ void testMultiplesLogAttribute(int fileCount, int flowsToLog, std::string verify
   record = plan->getCurrentFlowFile();
 
   REQUIRE(true == LogTestController::getInstance().contains("Size:8 Offset:0"));
-  REQUIRE(true == LogTestController::getInstance().contains("key:path value:" + std::string(dir)));
+  REQUIRE(true == LogTestController::getInstance().contains("key:path value:" + dir.string()));
   REQUIRE(true == LogTestController::getInstance().contains("Logged " + verifyStringFlowsLogged + " flow files"));
   LogTestController::getInstance().reset();
 }
@@ -427,7 +420,7 @@ TEST_CASE("Test Find file", "[getfileCreate3]") {
   plan->addProcessor(processorReport, "reporter", core::Relationship("success", "description"), false);
 
   auto dir = testController.createTempDirectory();
-  plan->setProperty(processor, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir);
+  plan->setProperty(processor, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir.string());
   testController.runSession(plan, false);
   auto records = plan->getProvenanceRecords();
   std::shared_ptr<core::FlowFile> record = plan->getCurrentFlowFile();
@@ -435,9 +428,8 @@ TEST_CASE("Test Find file", "[getfileCreate3]") {
   REQUIRE(records.empty());
 
   std::fstream file;
-  std::stringstream ss;
-  ss << dir << utils::file::FileUtils::get_separator() << "tstFile.ext";
-  file.open(ss.str(), std::ios::out);
+  auto path = dir / "tstFile.ext";
+  file.open(path, std::ios::out);
   file << "tempFile";
   file.close();
   plan->reset();
diff --git a/extensions/standard-processors/tests/unit/PutFileTests.cpp b/extensions/standard-processors/tests/unit/PutFileTests.cpp
index 75c5cd2f3..91bc4dcb7 100644
--- a/extensions/standard-processors/tests/unit/PutFileTests.cpp
+++ b/extensions/standard-processors/tests/unit/PutFileTests.cpp
@@ -66,20 +66,19 @@ TEST_CASE("PutFileTest", "[getfileputpfile]") {
 
   const auto dir = testController.createTempDirectory();
   const auto putfiledir = testController.createTempDirectory();
-  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir);
-  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), putfiledir);
+  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir.string());
+  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), putfiledir.string());
 
   testController.runSession(plan, false);
 
   auto records = plan->getProvenanceRecords();
   std::shared_ptr<core::FlowFile> record = plan->getCurrentFlowFile();
   REQUIRE(record == nullptr);
-  REQUIRE(records.size() == 0);
+  REQUIRE(records.empty());
 
   std::fstream file;
-  std::stringstream ss;
-  ss << dir << utils::file::get_separator() << "tstFile.ext";
-  file.open(ss.str(), std::ios::out);
+  auto path = dir / "tstFile.ext";
+  file.open(path, std::ios::out);
   file << "tempFile";
   file.close();
   plan->reset();
@@ -92,18 +91,18 @@ TEST_CASE("PutFileTest", "[getfileputpfile]") {
   record = plan->getCurrentFlowFile();
   testController.runSession(plan, false);
 
-  std::remove(ss.str().c_str());
+  std::filesystem::remove(path);
 
-  REQUIRE(true == LogTestController::getInstance().contains("key:absolute.path value:" + ss.str()));
+  REQUIRE(true == LogTestController::getInstance().contains("key:absolute.path value:" + path.string()));
   REQUIRE(true == LogTestController::getInstance().contains("Size:8 Offset:0"));
-  REQUIRE(true == LogTestController::getInstance().contains("key:path value:" + std::string(dir)));
+  REQUIRE(true == LogTestController::getInstance().contains("key:path value:" + dir.string()));
   // verify that the fle was moved
-  REQUIRE(false == std::ifstream(ss.str()).good());
-  std::stringstream movedFile;
-  movedFile << putfiledir << utils::file::get_separator() << "tstFile.ext";
-  REQUIRE(true == std::ifstream(movedFile.str()).good());
+  REQUIRE(false == std::ifstream(path).good());
+  auto moved_path = putfiledir / "tstFile.ext";
+
+  REQUIRE(true == std::ifstream(moved_path).good());
 
-  file.open(movedFile.str(), std::ios::in);
+  file.open(moved_path, std::ios::in);
   std::string contents((std::istreambuf_iterator<char>(file)),
                        std::istreambuf_iterator<char>());
   REQUIRE("tempFile" == contents);
@@ -128,27 +127,25 @@ TEST_CASE("PutFileTestFileExists", "[getfileputpfile]") {
   plan->addProcessor("LogAttribute", "logattribute", core::Relationship("failure", "description"), true);
 
   const auto dir = testController.createTempDirectory();
-  const auto putfiledir = testController.createTempDirectory();
-  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir);
-  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), putfiledir);
+  const auto put_file_dir = testController.createTempDirectory();
+  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir.string());
+  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), put_file_dir.string());
 
   testController.runSession(plan, false);
 
   auto records = plan->getProvenanceRecords();
   std::shared_ptr<core::FlowFile> record = plan->getCurrentFlowFile();
   REQUIRE(record == nullptr);
-  REQUIRE(records.size() == 0);
+  REQUIRE(records.empty());
 
   std::fstream file;
-  std::stringstream ss;
-  ss << dir << utils::file::get_separator() << "tstFile.ext";
-  file.open(ss.str(), std::ios::out);
+  auto path = dir / "tstFile.ext";
+  file.open(path, std::ios::out);
   file << "tempFile";
   file.close();
-//
-  std::stringstream movedFile;
-  movedFile << putfiledir << utils::file::get_separator() << "tstFile.ext";
-  file.open(movedFile.str(), std::ios::out);
+
+  auto moved_path = put_file_dir / "tstFile.ext";
+  file.open(moved_path, std::ios::out);
   file << "tempFile";
   file.close();
 
@@ -162,14 +159,14 @@ TEST_CASE("PutFileTestFileExists", "[getfileputpfile]") {
   record = plan->getCurrentFlowFile();
   testController.runSession(plan, false);
 
-  std::remove(ss.str().c_str());
+  std::filesystem::remove(path);
 
-  REQUIRE(true == LogTestController::getInstance().contains("key:absolute.path value:" + ss.str()));
+  REQUIRE(true == LogTestController::getInstance().contains("key:absolute.path value:" + path.string()));
   REQUIRE(true == LogTestController::getInstance().contains("Size:8 Offset:0"));
-  REQUIRE(true == LogTestController::getInstance().contains("key:path value:" + std::string(dir)));
+  REQUIRE(true == LogTestController::getInstance().contains("key:path value:" + dir.string()));
   // verify that the fle was moved
-  REQUIRE(false == std::ifstream(ss.str()).good());
-  REQUIRE(true == std::ifstream(movedFile.str()).good());
+  REQUIRE(false == std::ifstream(path).good());
+  REQUIRE(true == std::ifstream(moved_path).good());
 
   LogTestController::getInstance().reset();
 }
@@ -191,9 +188,9 @@ TEST_CASE("PutFileTestFileExistsIgnore", "[getfileputpfile]") {
   plan->addProcessor("LogAttribute", "logattribute", core::Relationship("success", "description"), true);
 
   const auto dir = testController.createTempDirectory();
-  const auto putfiledir = testController.createTempDirectory();
-  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir);
-  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), putfiledir);
+  const auto put_file_dir = testController.createTempDirectory();
+  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir.string());
+  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), put_file_dir.string());
   plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::ConflictResolution.getName(), "ignore");
 
   testController.runSession(plan, false);
@@ -201,21 +198,19 @@ TEST_CASE("PutFileTestFileExistsIgnore", "[getfileputpfile]") {
   auto records = plan->getProvenanceRecords();
   std::shared_ptr<core::FlowFile> record = plan->getCurrentFlowFile();
   REQUIRE(record == nullptr);
-  REQUIRE(records.size() == 0);
+  REQUIRE(records.empty());
 
   std::fstream file;
-  std::stringstream ss;
-  ss << dir << utils::file::get_separator() << "tstFile.ext";
-  file.open(ss.str(), std::ios::out);
+  auto path = dir / "tstFile.ext";
+  file.open(path, std::ios::out);
   file << "tempFile";
   file.close();
-//
-  std::stringstream movedFile;
-  movedFile << putfiledir << utils::file::get_separator() << "tstFile.ext";
-  file.open(movedFile.str(), std::ios::out);
+
+  auto moved_path = put_file_dir / "tstFile.ext";
+  file.open(moved_path, std::ios::out);
   file << "tempFile";
   file.close();
-  auto filemodtime = utils::file::last_write_time(movedFile.str());
+  auto file_mod_time = utils::file::last_write_time(moved_path);
 
   std::this_thread::sleep_for(std::chrono::milliseconds(1000));
   plan->reset();
@@ -228,15 +223,15 @@ TEST_CASE("PutFileTestFileExistsIgnore", "[getfileputpfile]") {
   record = plan->getCurrentFlowFile();
   testController.runSession(plan, false);
 
-  std::remove(ss.str().c_str());
+  std::filesystem::remove(path);
 
-  REQUIRE(true == LogTestController::getInstance().contains("key:absolute.path value:" + ss.str()));
+  REQUIRE(true == LogTestController::getInstance().contains("key:absolute.path value:" + path.string()));
   REQUIRE(true == LogTestController::getInstance().contains("Size:8 Offset:0"));
-  REQUIRE(true == LogTestController::getInstance().contains("key:path value:" + dir));
+  REQUIRE(true == LogTestController::getInstance().contains("key:path value:" + dir.string()));
   // verify that the fle was moved
-  REQUIRE(false == std::ifstream(ss.str()).good());
-  REQUIRE(true == std::ifstream(movedFile.str()).good());
-  REQUIRE(filemodtime == utils::file::last_write_time(movedFile.str()));
+  REQUIRE(false == std::ifstream(path).good());
+  REQUIRE(true == std::ifstream(moved_path).good());
+  REQUIRE(file_mod_time == utils::file::last_write_time(moved_path));
   LogTestController::getInstance().reset();
 }
 
@@ -257,9 +252,9 @@ TEST_CASE("PutFileTestFileExistsReplace", "[getfileputpfile]") {
   plan->addProcessor("LogAttribute", "logattribute", { core::Relationship("success", "d"), core::Relationship("failure", "d") }, true);
 
   const auto dir = testController.createTempDirectory();
-  const auto putfiledir = testController.createTempDirectory();
-  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir);
-  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), putfiledir);
+  const auto put_file_dir = testController.createTempDirectory();
+  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir.string());
+  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), put_file_dir.string());
   plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::ConflictResolution.getName(), "replace");
 
   testController.runSession(plan, false);
@@ -267,21 +262,19 @@ TEST_CASE("PutFileTestFileExistsReplace", "[getfileputpfile]") {
   auto records = plan->getProvenanceRecords();
   std::shared_ptr<core::FlowFile> record = plan->getCurrentFlowFile();
   REQUIRE(record == nullptr);
-  REQUIRE(records.size() == 0);
+  REQUIRE(records.empty());
 
   std::fstream file;
-  std::stringstream ss;
-  ss << dir << utils::file::get_separator() << "tstFile.ext";
-  file.open(ss.str(), std::ios::out);
+  auto path = dir / "tstFile.ext";
+  file.open(path, std::ios::out);
   file << "tempFile";
   file.close();
-//
-  std::stringstream movedFile;
-  movedFile << putfiledir << utils::file::get_separator() << "tstFile.ext";
-  file.open(movedFile.str(), std::ios::out);
+
+  auto moved_path = put_file_dir / "tstFile.ext";
+  file.open(moved_path, std::ios::out);
   file << "tempFile";
   file.close();
-  auto filemodtime = utils::file::last_write_time(movedFile.str());
+  auto file_mod_time = utils::file::last_write_time(moved_path);
 
   std::this_thread::sleep_for(std::chrono::milliseconds(1000));
   plan->reset();
@@ -294,27 +287,24 @@ TEST_CASE("PutFileTestFileExistsReplace", "[getfileputpfile]") {
   record = plan->getCurrentFlowFile();
   testController.runSession(plan, false);
 
-  std::remove(ss.str().c_str());
+  std::filesystem::remove(path);
 
-  REQUIRE(true == LogTestController::getInstance().contains("key:absolute.path value:" + ss.str()));
+  REQUIRE(true == LogTestController::getInstance().contains("key:absolute.path value:" + path.string()));
   REQUIRE(true == LogTestController::getInstance().contains("Size:8 Offset:0"));
-  REQUIRE(true == LogTestController::getInstance().contains("key:path value:" + std::string(dir)));
+  REQUIRE(true == LogTestController::getInstance().contains("key:path value:" + dir.string()));
   // verify that the fle was moved
-  REQUIRE(false == std::ifstream(ss.str()).good());
-  REQUIRE(true == std::ifstream(movedFile.str()).good());
+  REQUIRE(false == std::ifstream(path).good());
+  REQUIRE(true == std::ifstream(moved_path).good());
 #ifndef WIN32
-  REQUIRE(filemodtime != utils::file::last_write_time(movedFile.str()));
+  REQUIRE(file_mod_time != utils::file::last_write_time(moved_path));
 #endif
   LogTestController::getInstance().reset();
 }
 
 TEST_CASE("Test generation of temporary write path", "[putfileTmpWritePath]") {
   auto processor = std::make_shared<org::apache::nifi::minifi::processors::PutFile>("processorname");
-  std::stringstream prefix;
-  prefix << "a" << utils::file::get_separator() << "b" << utils::file::get_separator();
-  std::string path = prefix.str() + "c";
-  std::string expected_path = prefix.str() + ".c";
-  REQUIRE(processor->tmpWritePath(path, "").substr(1, expected_path.length()) == expected_path);
+  std::filesystem::path path = std::filesystem::path("a") / std::string("b") / "";
+  CHECK(processor->tmpWritePath(path, "").string().starts_with(path.string()));
 }
 
 TEST_CASE("PutFileMaxFileCountTest", "[getfileputpfilemaxcount]") {
@@ -335,18 +325,17 @@ TEST_CASE("PutFileMaxFileCountTest", "[getfileputpfilemaxcount]") {
 
   const auto dir = testController.createTempDirectory();
   const auto putfiledir = testController.createTempDirectory();
-  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir);
+  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir.string());
   plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::BatchSize.getName(), "1");
-  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), putfiledir);
+  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), putfiledir.string());
   plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::MaxDestFiles.getName(), "1");
 
 
 
   for (int i = 0; i < 2; ++i) {
-    std::stringstream ss;
-    ss << dir << utils::file::get_separator() << "tstFile" << i << ".ext";
+    auto path = dir / ("tstFile" + std::to_string(i) + ".ext");
     std::fstream file;
-    file.open(ss.str(), std::ios::out);
+    file.open(path, std::ios::out);
     file << "tempFile";
     file.close();
   }
@@ -360,18 +349,17 @@ TEST_CASE("PutFileMaxFileCountTest", "[getfileputpfilemaxcount]") {
   testController.runSession(plan);
 
 
-  REQUIRE(LogTestController::getInstance().contains("key:absolute.path value:" + std::string(dir) + utils::file::get_separator() + "tstFile0.ext"));
-  REQUIRE(LogTestController::getInstance().contains("Size:8 Offset:0"));
-  REQUIRE(LogTestController::getInstance().contains("key:path value:" + std::string(dir)));
+  CHECK(LogTestController::getInstance().contains("key:absolute.path value:" + (dir / "tstFile0.ext").string()));
+  CHECK(LogTestController::getInstance().contains("Size:8 Offset:0"));
+  CHECK(LogTestController::getInstance().contains("key:path value:" + dir.string()));
 
   // Only 1 of the 2 files should make it to the target dir
-  // Non-determistic, so let's just count them
+  // Non-deterministic, so let's just count them
   int files_in_dir = 0;
 
   for (int i = 0; i < 2; ++i) {
-    std::stringstream ss;
-    ss << putfiledir << utils::file::get_separator() << "tstFile" << i << ".ext";
-    std::ifstream file(ss.str());
+    auto path = putfiledir / ("tstFile" + std::to_string(i) + ".ext");
+    std::ifstream file(path);
     if (file.is_open() && file.good()) {
       files_in_dir++;
       file.close();
@@ -401,20 +389,20 @@ TEST_CASE("PutFileEmptyTest", "[EmptyFilePutTest]") {
   const auto dir = testController.createTempDirectory();
   const auto putfiledir = testController.createTempDirectory();
 
-  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir);
-  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), putfiledir);
+  plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir.string());
+  plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), putfiledir.string());
 
-  std::ofstream of(std::string(dir) + utils::file::get_separator() + "tstFile.ext");
+  std::ofstream of(dir / "tstFile.ext");
   of.close();
 
   plan->runNextProcessor();  // Get
   plan->runNextProcessor();  // Put
 
-  std::ifstream is(std::string(putfiledir) + utils::file::get_separator() + "tstFile.ext", std::ifstream::binary);
+  std::ifstream is(putfiledir / "tstFile.ext", std::ifstream::binary);
 
   REQUIRE(is.is_open());
   is.seekg(0, is.end);
-  REQUIRE(is.tellg() == 0);
+  CHECK(is.tellg() == 0);
 }
 
 #ifndef WIN32
@@ -432,7 +420,7 @@ TEST_CASE("TestPutFilePermissions", "[PutFilePermissions]") {
   std::shared_ptr<core::Processor> putfile = plan->addProcessor("PutFile", "putfile", core::Relationship("success", "description"), true);
 
   const auto dir = testController.createTempDirectory();
-  const auto putfiledir = testController.createTempDirectory() + utils::file::get_separator() + "test_dir";
+  const auto putfiledir = testController.createTempDirectory() / "test_dir";
 
   plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir);
   plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), putfiledir);
@@ -440,19 +428,19 @@ TEST_CASE("TestPutFilePermissions", "[PutFilePermissions]") {
   plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::DirectoryPermissions.getName(), "0777");
 
   std::fstream file;
-  file.open(std::string(dir) + utils::file::FileUtils::get_separator() + "tstFile.ext", std::ios::out);
+  file.open(dir / "tstFile.ext", std::ios::out);
   file << "tempFile";
   file.close();
 
   plan->runNextProcessor();  // Get
   plan->runNextProcessor();  // Put
 
-  auto path = std::string(putfiledir) + utils::file::FileUtils::get_separator() + "tstFile.ext";
+  auto path = putfiledir / "tstFile.ext";
   uint32_t perms = 0;
-  REQUIRE(utils::file::FileUtils::get_permissions(path, perms));
-  REQUIRE(perms == 0644);
-  REQUIRE(utils::file::FileUtils::get_permissions(putfiledir, perms));
-  REQUIRE(perms == 0777);
+  CHECK(utils::file::FileUtils::get_permissions(path, perms));
+  CHECK(perms == 0644);
+  CHECK(utils::file::FileUtils::get_permissions(putfiledir, perms));
+  CHECK(perms == 0777);
 }
 
 TEST_CASE("PutFileCreateDirectoryTest", "[PutFileProperties]") {
@@ -470,8 +458,8 @@ TEST_CASE("PutFileCreateDirectoryTest", "[PutFileProperties]") {
 
   // Define Directory
   auto dir = testController.createTempDirectory();
-  // Defining a sub directory
-  auto putfiledir = testController.createTempDirectory() + utils::file::FileUtils::get_separator() + "test_dir";
+  // Defining a subdirectory
+  auto putfiledir = testController.createTempDirectory() / "test_dir";
 
   plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::Directory.getName(), putfiledir);
   plan->setProperty(getfile, org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir);
@@ -479,9 +467,9 @@ TEST_CASE("PutFileCreateDirectoryTest", "[PutFileProperties]") {
   SECTION("with an empty file and create directory property set to true") {
     plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::CreateDirs.getName(), "true");
 
-    std::ofstream of(std::string(dir) + utils::file::FileUtils::get_separator() + "tstFile.ext");
+    std::ofstream of(dir / "tstFile.ext");
     of.close();
-    auto path = std::string(putfiledir) + utils::file::FileUtils::get_separator() + "tstFile.ext";
+    auto path = putfiledir / "tstFile.ext";
 
     plan->runNextProcessor();
     plan->runNextProcessor();
@@ -494,26 +482,26 @@ TEST_CASE("PutFileCreateDirectoryTest", "[PutFileProperties]") {
     plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::CreateDirs.getName(), "false");
     putfile->setAutoTerminatedRelationships(std::array{core::Relationship("failure", "description")});
 
-    std::ofstream of(std::string(dir) + utils::file::FileUtils::get_separator() + "tstFile.ext");
+    std::ofstream of(dir / "tstFile.ext");
     of.close();
-    auto path = std::string(putfiledir) + utils::file::FileUtils::get_separator() + "tstFile.ext";
+    auto path = putfiledir / "tstFile.ext";
 
     plan->runNextProcessor();
     plan->runNextProcessor();
 
     REQUIRE_FALSE(utils::file::exists(putfiledir));
     REQUIRE_FALSE(utils::file::exists(path));
-    std::string check = "Failed to create empty file: " + path;
+    std::string check = "Failed to create empty file: " + path.string();
     REQUIRE(LogTestController::getInstance().contains(check));
   }
 
   SECTION("with a non-empty file and create directory property set to true") {
     plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::CreateDirs.getName(), "true");
 
-    std::ofstream of(std::string(dir) + utils::file::FileUtils::get_separator() + "tstFile.ext");
+    std::ofstream of(dir / "tstFile.ext");
     of << "tempFile";
     of.close();
-    auto path = std::string(putfiledir) + utils::file::FileUtils::get_separator() + "tstFile.ext";
+    auto path = putfiledir / "tstFile.ext";
 
     plan->runNextProcessor();
     plan->runNextProcessor();
@@ -526,17 +514,17 @@ TEST_CASE("PutFileCreateDirectoryTest", "[PutFileProperties]") {
     plan->setProperty(putfile, org::apache::nifi::minifi::processors::PutFile::CreateDirs.getName(), "false");
     putfile->setAutoTerminatedRelationships(std::array{core::Relationship("failure", "description")});
 
-    std::ofstream of(std::string(dir) + utils::file::FileUtils::get_separator() + "tstFile.ext");
+    std::ofstream of(dir / "tstFile.ext");
     of << "tempFile";
     of.close();
-    auto path = std::string(putfiledir) + utils::file::FileUtils::get_separator() + "tstFile.ext";
+    auto path = putfiledir / "tstFile.ext";
 
     plan->runNextProcessor();
     plan->runNextProcessor();
 
     REQUIRE_FALSE(utils::file::exists(putfiledir));
     REQUIRE_FALSE(utils::file::exists(path));
-    std::string check = "PutFile commit put file operation to " + path + " failed because write failed";
+    std::string check = "PutFile commit put file operation to " + path.string() + " failed because write failed";
     REQUIRE(LogTestController::getInstance().contains(check));
   }
 }
diff --git a/extensions/standard-processors/tests/unit/RetryFlowFileTests.cpp b/extensions/standard-processors/tests/unit/RetryFlowFileTests.cpp
index 552c414b7..daf2f1abe 100644
--- a/extensions/standard-processors/tests/unit/RetryFlowFileTests.cpp
+++ b/extensions/standard-processors/tests/unit/RetryFlowFileTests.cpp
@@ -71,16 +71,16 @@ class RetryFlowFileTest {
   }
 
   void retryRoutingTest(
-      optional<std::string> /*exp_retry_prop_name*/,
+      const optional<std::string>& /*exp_retry_prop_name*/,
       optional<int> /*exp_retry_prop_val*/,
-      core::Relationship exp_outbound_relationship,
+      const core::Relationship& exp_outbound_relationship,
       bool exp_penalty_on_flowfile,
-      optional<std::string> retry_attr_name_on_flowfile,
-      optional<std::string> retry_attribute_value_before_processing,
+      const optional<std::string>& retry_attr_name_on_flowfile,
+      const optional<std::string>& retry_attribute_value_before_processing,
       optional<int> maximum_retries,
       optional<bool> penalize_retries,
       optional<bool> fail_on_non_numerical_overwrite,
-      optional<std::string> reuse_mode,
+      const optional<std::string>& reuse_mode,
       optional<bool> processor_uuid_matches_flowfile) {
     reInitialize();
 
@@ -136,13 +136,13 @@ class RetryFlowFileTest {
     plan_->setProperty(retryflowfile, "retries_exceeded_property_key_1", "retries_exceeded_property_value_1", true);
     plan_->setProperty(retryflowfile, "retries_exceeded_property_key_2", "retries_exceeded_property_value_2", true);
 
-    const std::string retry_dir            = testController_->createTempDirectory();
-    const std::string retries_exceeded_dir = testController_->createTempDirectory();
-    const std::string failure_dir          = testController_->createTempDirectory();
+    const auto retry_dir            = testController_->createTempDirectory();
+    const auto retries_exceeded_dir = testController_->createTempDirectory();
+    const auto failure_dir          = testController_->createTempDirectory();
 
-    plan_->setProperty(putfile_on_retry, PutFile::Directory.getName(), retry_dir);
-    plan_->setProperty(putfile_on_retries_exceeded, PutFile::Directory.getName(), retries_exceeded_dir);
-    plan_->setProperty(putfile_on_failure, PutFile::Directory.getName(), failure_dir);
+    plan_->setProperty(putfile_on_retry, PutFile::Directory.getName(), retry_dir.string());
+    plan_->setProperty(putfile_on_retries_exceeded, PutFile::Directory.getName(), retries_exceeded_dir.string());
+    plan_->setProperty(putfile_on_failure, PutFile::Directory.getName(), failure_dir.string());
 
     plan_->runNextProcessor();  // GenerateFlowFile
     plan_->runNextProcessor();  // UpdateAttribute
@@ -162,7 +162,7 @@ class RetryFlowFileTest {
     REQUIRE(expect_warning_on_reuse == retryFlowfileWarnedForReuse());
   }
 
-  static bool logContainsText(const std::string pattern) {
+  static bool logContainsText(const std::string& pattern) {
     const std::string logs = LogTestController::getInstance().log_output.str();
     return logs.find(pattern) != std::string::npos;
   }
diff --git a/extensions/standard-processors/tests/unit/TailFileTests.cpp b/extensions/standard-processors/tests/unit/TailFileTests.cpp
index ccfba30ec..53c1d10ac 100644
--- a/extensions/standard-processors/tests/unit/TailFileTests.cpp
+++ b/extensions/standard-processors/tests/unit/TailFileTests.cpp
@@ -59,7 +59,7 @@ static const std::string NEW_TAIL_DATA = "newdata\n";
 static const std::string ADDITIONALY_CREATED_FILE_CONTENT = "additional file data\n";
 
 namespace {
-std::string createTempFile(const std::filesystem::path& directory, const std::filesystem::path& file_name, const std::string& contents,
+std::filesystem::path createTempFile(const std::filesystem::path& directory, const std::filesystem::path& file_name, const std::string& contents,
     std::ios_base::openmode open_mode = std::ios::out | std::ios::binary) {
   if (!utils::file::exists(directory.string())) {
     std::filesystem::create_directories(directory);
@@ -70,21 +70,11 @@ std::string createTempFile(const std::filesystem::path& directory, const std::fi
   return full_file_name;
 }
 
-void appendTempFile(const std::string &directory, const std::string &file_name, const std::string &contents,
+void appendTempFile(const std::filesystem::path& directory, const std::filesystem::path& file_name, const std::string& contents,
     std::ios_base::openmode open_mode = std::ios::app | std::ios::binary) {
   createTempFile(directory, file_name, contents, open_mode);
 }
 
-void removeFile(const std::string &directory, const std::string &file_name) {
-  std::string full_file_name = directory + utils::file::get_separator() + file_name;
-  std::remove(full_file_name.c_str());
-}
-
-void renameTempFile(const std::string &directory, const std::string &old_file_name, const std::string &new_file_name) {
-  std::string old_full_file_name = directory + utils::file::get_separator() + old_file_name;
-  std::string new_full_file_name = directory + utils::file::get_separator() + new_file_name;
-  rename(old_full_file_name.c_str(), new_full_file_name.c_str());
-}
 }  // namespace
 
 TEST_CASE("TailFile reads the file until the first delimiter", "[simple]") {
@@ -99,18 +89,14 @@ TEST_CASE("TailFile reads the file until the first delimiter", "[simple]") {
   plan->addProcessor("LogAttribute", "logattribute", core::Relationship("success", "description"), true);
 
   auto dir = testController.createTempDirectory();
-  std::stringstream temp_file;
-  temp_file << dir << utils::file::get_separator() << TMP_FILE;
+  auto temp_file_path = dir / TMP_FILE;
 
   std::ofstream tmpfile;
-  tmpfile.open(temp_file.str(), std::ios::out | std::ios::binary);
+  tmpfile.open(temp_file_path, std::ios::out | std::ios::binary);
   tmpfile << NEWLINE_FILE;
   tmpfile.close();
 
-  std::stringstream state_file;
-  state_file << dir << utils::file::get_separator() << STATE_FILE;
-
-  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file.str());
+  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file_path.string());
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::Delimiter.getName(), "\n");
 
   testController.runSession(plan, false);
@@ -139,15 +125,14 @@ TEST_CASE("TailFile picks up the second line if a delimiter is written between r
   plan->addProcessor("LogAttribute", "logattribute", core::Relationship("success", "description"), true);
 
   auto dir = testController.createTempDirectory();
-  std::stringstream temp_file;
-  temp_file << dir << utils::file::get_separator() << TMP_FILE;
+  auto temp_file_path = dir / TMP_FILE;
 
   std::ofstream tmpfile;
-  tmpfile.open(temp_file.str(), std::ios::out | std::ios::binary);
+  tmpfile.open(temp_file_path, std::ios::out | std::ios::binary);
   tmpfile << NEWLINE_FILE;
   tmpfile.close();
 
-  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file.str());
+  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file_path.string());
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::Delimiter.getName(), "\n");
 
   testController.runSession(plan, true);
@@ -158,7 +143,7 @@ TEST_CASE("TailFile picks up the second line if a delimiter is written between r
   LogTestController::getInstance().resetStream(LogTestController::getInstance().log_output);
 
   std::ofstream appendStream;
-  appendStream.open(temp_file.str(), std::ios_base::app | std::ios_base::binary);
+  appendStream.open(temp_file_path, std::ios_base::app | std::ios_base::binary);
   appendStream << std::endl;
   testController.runSession(plan, true);
 
@@ -182,15 +167,14 @@ TEST_CASE("TailFile re-reads the file if the state is deleted between runs", "[s
   plan->addProcessor("LogAttribute", "logattribute", core::Relationship("success", "description"), true);
 
   auto dir = testController.createTempDirectory();
-  std::stringstream temp_file;
-  temp_file << dir << utils::file::get_separator() << TMP_FILE;
+  auto temp_file_path = dir / TMP_FILE;
 
   std::ofstream tmpfile;
-  tmpfile.open(temp_file.str(), std::ios::out | std::ios::binary);
+  tmpfile.open(temp_file_path, std::ios::out | std::ios::binary);
   tmpfile << NEWLINE_FILE;
   tmpfile.close();
 
-  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file.str());
+  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file_path.string());
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::Delimiter.getName(), "\n");
 
   testController.runSession(plan, true);
@@ -222,37 +206,35 @@ TEST_CASE("TailFile picks up the state correctly if it is rewritten between runs
   plan->addProcessor("LogAttribute", "logattribute", core::Relationship("success", "description"), true);
 
   auto dir = testController.createTempDirectory();
-  std::stringstream temp_file;
-  temp_file << dir << utils::file::get_separator() << TMP_FILE;
+  auto temp_file_path = dir / TMP_FILE;
 
   std::ofstream tmpfile;
-  tmpfile.open(temp_file.str(), std::ios::out | std::ios::binary);
+  tmpfile.open(temp_file_path, std::ios::out | std::ios::binary);
   tmpfile << NEWLINE_FILE;
   tmpfile.close();
 
   std::ofstream appendStream;
-  appendStream.open(temp_file.str(), std::ios_base::app | std::ios_base::binary);
+  appendStream.open(temp_file_path, std::ios_base::app | std::ios_base::binary);
   appendStream.write("\n", 1);
   appendStream.close();
 
-  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file.str());
+  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file_path.string());
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::Delimiter.getName(), "\n");
 
   testController.runSession(plan, true);
   REQUIRE(LogTestController::getInstance().contains("key:filename value:minifi-tmpfile.0-13.txt"));
 
-  std::string filePath;
-  std::string fileName;
-  REQUIRE(utils::file::getFileNameAndPath(temp_file.str(), filePath, fileName));
+  REQUIRE(temp_file_path.has_filename());
+  REQUIRE(temp_file_path.has_parent_path());
 
   // should stay the same
   for (int i = 0; i < 5; i++) {
     plan->reset(true);  // start a new but with state file
     LogTestController::getInstance().resetStream(LogTestController::getInstance().log_output);
 
-    plan->getProcessContextForProcessor(tailfile)->getStateManager()->set({{"file.0.name", fileName},
+    plan->getProcessContextForProcessor(tailfile)->getStateManager()->set({{"file.0.name", temp_file_path.filename().string()},
                                                                                    {"file.0.position", "14"},
-                                                                                   {"file.0.current", temp_file.str()}});
+                                                                                   {"file.0.current", temp_file_path.string()}});
 
     testController.runSession(plan, true);
 
@@ -262,9 +244,9 @@ TEST_CASE("TailFile picks up the state correctly if it is rewritten between runs
   for (int i = 14; i < 34; i++) {
     plan->reset(true);  // start a new but with state file
 
-    plan->getProcessContextForProcessor(tailfile)->getStateManager()->set({{"file.0.name", fileName},
+    plan->getProcessContextForProcessor(tailfile)->getStateManager()->set({{"file.0.name", temp_file_path.filename().string()},
                                                                                    {"file.0.position", std::to_string(i)},
-                                                                                   {"file.0.current", temp_file.str()}});
+                                                                                   {"file.0.current", temp_file_path.string()}});
 
     testController.runSession(plan, true);
   }
@@ -291,30 +273,29 @@ TEST_CASE("TailFile converts the old-style state file to the new-style state", "
   plan->setProperty(logattribute, org::apache::nifi::minifi::processors::LogAttribute::FlowFilesToLog.getName(), "0");
 
   auto dir = testController.createTempDirectory();
-  std::stringstream state_file;
-  state_file << dir << utils::file::get_separator() << STATE_FILE;
+  auto state_file_path = dir / STATE_FILE;
 
-  auto statefile = state_file.str() + "." + id;
+  auto new_state_file_path = state_file_path.string() + ("." + id);
 
   SECTION("single") {
-    const std::string temp_file = createTempFile(dir, TMP_FILE, NEWLINE_FILE + '\n');
+    const auto temp_file = createTempFile(dir, TMP_FILE, NEWLINE_FILE + '\n');
 
-    plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file);
-    plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::StateFile.getName(), state_file.str());
+    plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file.string());
+    plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::StateFile.getName(), state_file_path.string());
     plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::Delimiter.getName(), "\n");
 
-    std::ofstream newstatefile;
-    newstatefile.open(statefile);
+    std::ofstream new_state_file;
+    new_state_file.open(new_state_file_path);
     SECTION("legacy") {
-      newstatefile << "FILENAME=" << temp_file << std::endl;
-      newstatefile << "POSITION=14" << std::endl;
+      new_state_file << "FILENAME=" << temp_file.string() << std::endl;
+      new_state_file << "POSITION=14" << std::endl;
     }
     SECTION("newer single") {
-      newstatefile << "FILENAME=" << TMP_FILE << std::endl;
-      newstatefile << "POSITION." << TMP_FILE << "=14" << std::endl;
-      newstatefile << "CURRENT." << TMP_FILE << "=" << temp_file << std::endl;
+      new_state_file << "FILENAME=" << TMP_FILE << std::endl;
+      new_state_file << "POSITION." << TMP_FILE << "=14" << std::endl;
+      new_state_file << "CURRENT." << TMP_FILE << "=" << temp_file.string() << std::endl;
     }
-    newstatefile.close();
+    new_state_file.close();
 
     testController.runSession(plan, true);
     REQUIRE(LogTestController::getInstance().contains("key:filename value:minifi-tmpfile.14-34.txt"));
@@ -322,12 +303,11 @@ TEST_CASE("TailFile converts the old-style state file to the new-style state", "
     std::unordered_map<std::string, std::string> state;
     REQUIRE(plan->getProcessContextForProcessor(tailfile)->getStateManager()->get(state));
 
-    std::string filePath;
-    std::string fileName;
-    REQUIRE(utils::file::getFileNameAndPath(temp_file, filePath, fileName));
-    std::unordered_map<std::string, std::string> expected_state{{"file.0.name", fileName},
+    REQUIRE(temp_file.has_filename());
+    REQUIRE(temp_file.has_parent_path());
+    std::unordered_map<std::string, std::string> expected_state{{"file.0.name", temp_file.filename().string()},
                                                                 {"file.0.position", "35"},
-                                                                {"file.0.current", temp_file},
+                                                                {"file.0.current", temp_file.string()},
                                                                 {"file.0.checksum", "1404369522"}};
     for (const auto& key_value_pair : expected_state) {
       const auto it = state.find(key_value_pair.first);
@@ -340,24 +320,24 @@ TEST_CASE("TailFile converts the old-style state file to the new-style state", "
   SECTION("multiple") {
     const std::string file_name_1 = "bar.txt";
     const std::string file_name_2 = "foo.txt";
-    const std::string temp_file_1 = createTempFile(dir, file_name_1, NEWLINE_FILE + '\n');
-    const std::string temp_file_2 = createTempFile(dir, file_name_2, NEWLINE_FILE + '\n');
+    const auto temp_file_1 = createTempFile(dir, file_name_1, NEWLINE_FILE + '\n');
+    const auto temp_file_2 = createTempFile(dir, file_name_2, NEWLINE_FILE + '\n');
 
     plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::TailMode.getName(), "Multiple file");
-    plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::BaseDirectory.getName(), dir);
+    plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::BaseDirectory.getName(), dir.string());
     plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::LookupFrequency.getName(), "0 sec");
     plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), ".*\\.txt");
-    plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::StateFile.getName(), state_file.str());
+    plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::StateFile.getName(), state_file_path.string());
     plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::Delimiter.getName(), "\n");
 
     std::ofstream newstatefile;
-    newstatefile.open(statefile);
+    newstatefile.open(new_state_file_path);
     newstatefile << "FILENAME=" << file_name_1 << std::endl;
     newstatefile << "POSITION." << file_name_1 << "=14" << std::endl;
-    newstatefile << "CURRENT." << file_name_1 << "=" << temp_file_1 << std::endl;
+    newstatefile << "CURRENT." << file_name_1 << "=" << temp_file_1.string() << std::endl;
     newstatefile << "FILENAME=" << file_name_2 << std::endl;
     newstatefile << "POSITION." << file_name_2 << "=15" << std::endl;
-    newstatefile << "CURRENT." << file_name_2 << "=" << temp_file_2 << std::endl;
+    newstatefile << "CURRENT." << file_name_2 << "=" << temp_file_2.string() << std::endl;
     newstatefile.close();
 
     testController.runSession(plan, true);
@@ -367,19 +347,17 @@ TEST_CASE("TailFile converts the old-style state file to the new-style state", "
     std::unordered_map<std::string, std::string> state;
     REQUIRE(plan->getProcessContextForProcessor(tailfile)->getStateManager()->get(state));
 
-    std::string filePath1;
-    std::string filePath2;
-    std::string fileName1;
-    std::string fileName2;
-    REQUIRE(utils::file::getFileNameAndPath(temp_file_1, filePath1, fileName1));
-    REQUIRE(utils::file::getFileNameAndPath(temp_file_2, filePath2, fileName2));
-    std::unordered_map<std::string, std::string> expected_state{{"file.0.name", fileName1},
+    REQUIRE(temp_file_1.has_parent_path());
+    REQUIRE(temp_file_1.has_filename());
+    REQUIRE(temp_file_2.has_parent_path());
+    REQUIRE(temp_file_2.has_filename());
+    std::unordered_map<std::string, std::string> expected_state{{"file.0.name", temp_file_1.filename().string()},
                                                                 {"file.0.position", "35"},
-                                                                {"file.0.current", temp_file_1},
+                                                                {"file.0.current", temp_file_1.string()},
                                                                 {"file.0.checksum", "1404369522"},
-                                                                {"file.1.name", fileName2},
+                                                                {"file.1.name", temp_file_2.filename().string()},
                                                                 {"file.1.position", "35"},
-                                                                {"file.1.current", temp_file_2},
+                                                                {"file.1.current", temp_file_2.string()},
                                                                 {"file.1.checksum", "2289158555"}};
     for (const auto& key_value_pair : expected_state) {
       const auto it = state.find(key_value_pair.first);
@@ -403,16 +381,16 @@ TEST_CASE("TailFile picks up the new File to Tail if it is changed between runs"
   std::shared_ptr<core::Processor> log_attribute = plan->addProcessor("LogAttribute", "log_attribute", core::Relationship("success", "description"), true);
   plan->setProperty(log_attribute, org::apache::nifi::minifi::processors::LogAttribute::FlowFilesToLog.getName(), "0");
 
-  std::string directory = testController.createTempDirectory();
-  std::string first_test_file = createTempFile(directory, "first.log", "my first log line\n");
-  plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), first_test_file);
+  auto directory = testController.createTempDirectory();
+  auto first_test_file = createTempFile(directory, "first.log", "my first log line\n");
+  plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), first_test_file.string());
   testController.runSession(plan, true);
   REQUIRE(LogTestController::getInstance().contains("Logged 1 flow file"));
   REQUIRE(LogTestController::getInstance().contains("key:filename value:first.0-17.log"));
 
   SECTION("The new file gets picked up") {
-    std::string second_test_file = createTempFile(directory, "second.log", "my second log line\n");
-    plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), second_test_file);
+    auto second_test_file = createTempFile(directory, "second.log", "my second log line\n");
+    plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), second_test_file.string());
     plan->reset(true);  // clear the memory, but keep the state file
     LogTestController::getInstance().resetStream(LogTestController::getInstance().log_output);
     testController.runSession(plan, true);
@@ -422,8 +400,8 @@ TEST_CASE("TailFile picks up the new File to Tail if it is changed between runs"
 
   SECTION("The old file will no longer be tailed") {
     appendTempFile(directory, "first.log", "add some more stuff\n");
-    std::string second_test_file = createTempFile(directory, "second.log", "");
-    plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), second_test_file);
+    auto second_test_file = createTempFile(directory, "second.log", "");
+    plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), second_test_file.string());
     plan->reset(true);  // clear the memory, but keep the state file
     LogTestController::getInstance().resetStream(LogTestController::getInstance().log_output);
     testController.runSession(plan, true);
@@ -436,13 +414,13 @@ TEST_CASE("TailFile picks up the new File to Tail if it is changed between runs
   LogTestController::getInstance().setDebug<minifi::processors::TailFile>();
   LogTestController::getInstance().setDebug<minifi::processors::LogAttribute>();
 
-  std::string directory = testController.createTempDirectory();
+  auto directory = testController.createTempDirectory();
 
   std::shared_ptr<TestPlan> plan = testController.createPlan();
   std::shared_ptr<core::Processor> tail_file = plan->addProcessor("TailFile", "tail_file");
   plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::Delimiter.getName(), "\n");
   plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::TailMode.getName(), "Multiple file");
-  plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::BaseDirectory.getName(), directory);
+  plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::BaseDirectory.getName(), directory.string());
   plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::LookupFrequency.getName(), "0 sec");
   plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), "first\\..*\\.log");
 
@@ -493,21 +471,20 @@ TEST_CASE("TailFile finds the single input file in both Single and Multiple mode
   plan->addProcessor("LogAttribute", "logattribute", core::Relationship("success", "description"), true);
 
   auto dir = testController.createTempDirectory();
-  std::stringstream temp_file;
-  temp_file << dir << utils::file::get_separator() << TMP_FILE;
+  auto temp_file_path = dir / TMP_FILE;
   std::ofstream tmpfile;
-  tmpfile.open(temp_file.str(), std::ios::out | std::ios::binary);
+  tmpfile.open(temp_file_path, std::ios::out | std::ios::binary);
   tmpfile << NEWLINE_FILE;
   tmpfile.close();
 
   SECTION("Single") {
-    plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file.str());
+    plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file_path.string());
   }
 
   SECTION("Multiple") {
     plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), "minifi-.*\\.txt");
     plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::TailMode.getName(), "Multiple file");
-    plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::BaseDirectory.getName(), dir);
+    plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::BaseDirectory.getName(), dir.string());
     plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::LookupFrequency.getName(), "0 sec");
   }
 
@@ -534,7 +511,7 @@ TEST_CASE("TailFile picks up new files created between runs", "[multiple_file]")
   std::shared_ptr<TestPlan> plan = testController.createPlan();
   std::shared_ptr<core::Processor> tailfile = plan->addProcessor("TailFile", "tailfile");
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::TailMode.getName(), "Multiple file");
-  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::BaseDirectory.getName(), dir);
+  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::BaseDirectory.getName(), dir.string());
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::LookupFrequency.getName(), "0 sec");
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), ".*\\.log");
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::Delimiter.getName(), "\n");
@@ -569,7 +546,7 @@ TEST_CASE("TailFile can handle input files getting removed", "[multiple_file]")
   std::shared_ptr<TestPlan> plan = testController.createPlan();
   std::shared_ptr<core::Processor> tailfile = plan->addProcessor("TailFile", "tailfile");
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::TailMode.getName(), "Multiple file");
-  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::BaseDirectory.getName(), dir);
+  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::BaseDirectory.getName(), dir.string());
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::LookupFrequency.getName(), "0 sec");
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), ".*\\.log");
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::Delimiter.getName(), "\n");
@@ -589,7 +566,7 @@ TEST_CASE("TailFile can handle input files getting removed", "[multiple_file]")
   LogTestController::getInstance().resetStream(LogTestController::getInstance().log_output);
 
   appendTempFile(dir, "one.log", "line two\nline three\nline four\n");
-  removeFile(dir, "two.log");
+  std::filesystem::remove(dir / "two.log");
 
   testController.runSession(plan, true);
   REQUIRE(LogTestController::getInstance().contains("Logged 3 flow files"));
@@ -620,14 +597,13 @@ TEST_CASE("TailFile processes a very long line correctly", "[simple]") {
   std::shared_ptr<core::Processor> tailfile = plan->addProcessor("TailFile", "tailfileProc");
 
   auto dir = testController.createTempDirectory();
-  std::stringstream temp_file;
-  temp_file << dir << utils::file::get_separator() << TMP_FILE;
+  auto temp_file_path = dir / TMP_FILE;
   std::ofstream tmpfile;
-  tmpfile.open(temp_file.str(), std::ios::out | std::ios::binary);
+  tmpfile.open(temp_file_path, std::ios::out | std::ios::binary);
   tmpfile << line1 << line2 << line3 << line4;
   tmpfile.close();
 
-  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file.str());
+  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file_path.string());
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::Delimiter.getName(), "\n");
 
   std::shared_ptr<core::Processor> log_attr = plan->addProcessor("LogAttribute", "Log", core::Relationship("success", "description"), true);
@@ -697,14 +673,13 @@ TEST_CASE("TailFile processes a long line followed by multiple newlines correctl
   std::shared_ptr<TestPlan> plan = testController.createPlan();
   std::shared_ptr<core::Processor> tailfile = plan->addProcessor("TailFile", "tailfileProc");
   auto dir = testController.createTempDirectory();
-  std::stringstream temp_file;
-  temp_file << dir << utils::file::get_separator() << TMP_FILE;
+  auto temp_file_path = dir / TMP_FILE;
   std::ofstream tmpfile;
-  tmpfile.open(temp_file.str(), std::ios::out | std::ios::binary);
+  tmpfile.open(temp_file_path, std::ios::out | std::ios::binary);
   tmpfile << line1 << line2 << line3 << line4;
   tmpfile.close();
 
-  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file.str());
+  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file_path.string());
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::Delimiter.getName(), "\n");
 
   std::shared_ptr<core::Processor> log_attr = plan->addProcessor("LogAttribute", "Log", core::Relationship("success", "description"), true);
@@ -790,9 +765,9 @@ TEST_CASE("TailFile onSchedule throws in Multiple mode if the Base Directory doe
   }
 
   SECTION("Base Directory is set and it exists") {
-    std::string directory = testController.createTempDirectory();
+    auto directory = testController.createTempDirectory();
 
-    plan->setProperty(tailfile, minifi::processors::TailFile::BaseDirectory.getName(), directory);
+    plan->setProperty(tailfile, minifi::processors::TailFile::BaseDirectory.getName(), directory.string());
     plan->setProperty(tailfile, minifi::processors::TailFile::LookupFrequency.getName(), "0 sec");
     REQUIRE_NOTHROW(plan->runNextProcessor());
   }
@@ -810,7 +785,7 @@ TEST_CASE("TailFile finds and finishes the renamed file and continues with the n
 
   auto plan = testController.createPlan();
   auto dir = testController.createTempDirectory();
-  std::string in_file = dir + utils::file::get_separator() + "testfifo.txt";
+  auto in_file = dir / "testfifo.txt";
 
   std::ofstream in_file_stream(in_file, std::ios::out | std::ios::binary);
   in_file_stream << NEWLINE_FILE;
@@ -821,12 +796,12 @@ TEST_CASE("TailFile finds and finishes the renamed file and continues with the n
   plan->setProperty(tail_file, minifi::processors::TailFile::Delimiter.getName(), std::string(1, DELIM));
 
   SECTION("single") {
-    plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), in_file);
+    plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), in_file.string());
   }
   SECTION("Multiple") {
     plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), "testfifo.txt");
     plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::TailMode.getName(), "Multiple file");
-    plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::BaseDirectory.getName(), dir);
+    plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::BaseDirectory.getName(), dir.string());
     plan->setProperty(tail_file, org::apache::nifi::minifi::processors::TailFile::LookupFrequency.getName(), "0 sec");
   }
 
@@ -845,9 +820,10 @@ TEST_CASE("TailFile finds and finishes the renamed file and continues with the n
   in_file_stream << DELIM;
   in_file_stream.close();
 
-  std::string rotated_file = (in_file + ".1");
+  auto rotated_file = in_file;
+  rotated_file += ".1";
 
-  REQUIRE(rename(in_file.c_str(), rotated_file.c_str()) == 0);
+  REQUIRE_NOTHROW(std::filesystem::rename(in_file, rotated_file));
 
   std::ofstream new_in_file_stream(in_file, std::ios::out | std::ios::binary);
   new_in_file_stream << "five" << DELIM << "six" << DELIM;
@@ -876,7 +852,7 @@ TEST_CASE("TailFile finds and finishes multiple rotated files and continues with
   LogTestController::getInstance().setTrace<minifi::processors::LogAttribute>();
   auto plan = testController.createPlan();
   auto dir = testController.createTempDirectory();
-  std::string test_file = dir + utils::file::FileUtils::get_separator() + "fruits.log";
+  auto test_file = dir / "fruits.log";
 
   std::ofstream test_file_stream_0(test_file, std::ios::binary);
   test_file_stream_0 << "Apple" << DELIM << "Orange" << DELIM;
@@ -885,7 +861,7 @@ TEST_CASE("TailFile finds and finishes multiple rotated files and continues with
   // Build MiNiFi processing graph
   auto tail_file = plan->addProcessor("TailFile", "Tail");
   plan->setProperty(tail_file, minifi::processors::TailFile::Delimiter.getName(), std::string(1, DELIM));
-  plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), test_file);
+  plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), test_file.string());
   auto log_attr = plan->addProcessor("LogAttribute", "Log", core::Relationship("success", "description"), true);
   plan->setProperty(log_attr, minifi::processors::LogAttribute::FlowFilesToLog.getName(), "0");
 
@@ -900,15 +876,15 @@ TEST_CASE("TailFile finds and finishes multiple rotated files and continues with
   test_file_stream_0 << "Pear" << DELIM;
   test_file_stream_0.close();
 
-  std::string first_rotated_file = dir + utils::file::FileUtils::get_separator() + "fruits.0.log";
-  REQUIRE(rename(test_file.c_str(), first_rotated_file.c_str()) == 0);
+  auto first_rotated_file = dir / "fruits.0.log";
+  REQUIRE_NOTHROW(std::filesystem::rename(test_file, first_rotated_file));
 
   std::ofstream test_file_stream_1(test_file, std::ios::binary);
   test_file_stream_1 << "Pineapple" << DELIM << "Kiwi" << DELIM;
   test_file_stream_1.close();
 
-  std::string second_rotated_file = dir + utils::file::FileUtils::get_separator() + "fruits.1.log";
-  REQUIRE(rename(test_file.c_str(), second_rotated_file.c_str()) == 0);
+  auto second_rotated_file = dir / "fruits.1.log";
+  REQUIRE_NOTHROW(std::filesystem::rename(test_file, second_rotated_file));
 
   std::ofstream test_file_stream_2(test_file, std::ios::binary);
   test_file_stream_2 << "Apricot" << DELIM;
@@ -932,12 +908,12 @@ TEST_CASE("TailFile ignores old rotated files", "[rotation]") {
   LogTestController::getInstance().setDebug<core::ProcessSession>();
   LogTestController::getInstance().setDebug<minifi::processors::LogAttribute>();
 
-  const std::string dir = testController.createTempDirectory();
-  std::string log_file_name = dir + utils::file::FileUtils::get_separator() + "test.log";
+  const auto dir = testController.createTempDirectory();
+  auto log_file_name = dir / "test.log";
 
   std::shared_ptr<TestPlan> plan = testController.createPlan();
   std::shared_ptr<core::Processor> tailfile = plan->addProcessor("TailFile", "tailfile");
-  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), log_file_name);
+  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), log_file_name.string());
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::Delimiter.getName(), "\n");
 
   std::shared_ptr<core::Processor> logattribute = plan->addProcessor("LogAttribute", "logattribute",
@@ -954,8 +930,8 @@ TEST_CASE("TailFile ignores old rotated files", "[rotation]") {
   REQUIRE(LogTestController::getInstance().contains("Logged 3 flow files"));
   REQUIRE(false == LogTestController::getInstance().contains("key:filename value:test.2019-08-20", std::chrono::seconds(0)));
 
-  std::string rotated_log_file_name = dir + utils::file::FileUtils::get_separator() + "test.2020-05-18";
-  REQUIRE(rename(log_file_name.c_str(), rotated_log_file_name.c_str()) == 0);
+  auto rotated_log_file_name = dir / "test.2020-05-18";
+  REQUIRE_NOTHROW(std::filesystem::rename(log_file_name, rotated_log_file_name));
 
   createTempFile(dir, "test.log", "line8\nline9\n");
 
@@ -987,7 +963,7 @@ TEST_CASE("TailFile rotation works with multiple input files", "[rotation][multi
   plan->setProperty(tail_file, minifi::processors::TailFile::Delimiter.getName(), "\n");
   plan->setProperty(tail_file, minifi::processors::TailFile::TailMode.getName(), "Multiple file");
   plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), ".*\\.log");
-  plan->setProperty(tail_file, minifi::processors::TailFile::BaseDirectory.getName(), dir);
+  plan->setProperty(tail_file, minifi::processors::TailFile::BaseDirectory.getName(), dir.string());
   plan->setProperty(tail_file, minifi::processors::TailFile::LookupFrequency.getName(), "0 sec");
 
   auto log_attribute = plan->addProcessor("LogAttribute", "Log", core::Relationship("success", "description"), true);
@@ -1012,8 +988,8 @@ TEST_CASE("TailFile rotation works with multiple input files", "[rotation][multi
   appendTempFile(dir, "animal.log", "axolotl\n");
   appendTempFile(dir, "color.log", "aquamarine\n");
 
-  renameTempFile(dir, "fruit.log", "fruit.0");
-  renameTempFile(dir, "animal.log", "animal.0");
+  std::filesystem::rename(dir / "fruit.log", dir / "fruit.0");
+  std::filesystem::rename(dir / "animal.log", dir / "animal.0");
 
   createTempFile(dir, "fruit.log", "peach\n");
   createTempFile(dir, "animal.log", "dinosaur\n");
@@ -1042,12 +1018,12 @@ TEST_CASE("TailFile handles the Rolling Filename Pattern property correctly", "[
 
   auto plan = testController.createPlan();
   auto dir = testController.createTempDirectory();
-  std::string test_file = createTempFile(dir, "test.log", "some stuff\n");
+  auto test_file = createTempFile(dir, "test.log", "some stuff\n");
 
   // Build MiNiFi processing graph
   auto tail_file = plan->addProcessor("TailFile", "Tail");
   plan->setProperty(tail_file, minifi::processors::TailFile::Delimiter.getName(), "\n");
-  plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), test_file);
+  plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), test_file.string());
 
   std::vector<std::string> expected_log_lines;
 
@@ -1082,7 +1058,7 @@ TEST_CASE("TailFile handles the Rolling Filename Pattern property correctly", "[
   std::this_thread::sleep_for(std::chrono::milliseconds(100));
 
   appendTempFile(dir, "test.log", "one more line\n");
-  renameTempFile(dir, "test.log", "test.rolled.log");
+  std::filesystem::rename(dir / "test.log", dir / "test.rolled.log");
   createTempFile(dir, "test.txt", "unrelated stuff\n");
   createTempFile(dir, "other_rolled.log", "some stuff\none more line\n");  // same contents as test.rolled.log
 
@@ -1105,9 +1081,9 @@ TEST_CASE("TailFile finds and finishes the renamed file and continues with the n
 
   auto log_dir = testController.createTempDirectory();
 
-  std::string test_file_1 = createTempFile(log_dir, "test.1", "line one\nline two\nline three\n");  // old rotated file
+  auto test_file_1 = createTempFile(log_dir, "test.1", "line one\nline two\nline three\n");  // old rotated file
   std::this_thread::sleep_for(std::chrono::seconds(1));
-  std::string test_file = createTempFile(log_dir, "test.log", "line four\nline five\nline six\n");  // current log file
+  auto test_file = createTempFile(log_dir, "test.log", "line four\nline five\nline six\n");  // current log file
 
   auto state_dir = testController.createTempDirectory();
 
@@ -1119,7 +1095,7 @@ TEST_CASE("TailFile finds and finishes the renamed file and continues with the n
   {
     auto test_plan = testController.createPlan(configuration, state_dir);
     auto tail_file = test_plan->addProcessor("TailFile", tail_file_uuid, "Tail", {success_relationship});
-    test_plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), test_file);
+    test_plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), test_file.string());
     auto log_attr = test_plan->addProcessor("LogAttribute", "Log", success_relationship, true);
     test_plan->setProperty(log_attr, minifi::processors::LogAttribute::FlowFilesToLog.getName(), "0");
     test_plan->setProperty(log_attr, minifi::processors::LogAttribute::LogPayload.getName(), "true");
@@ -1133,14 +1109,14 @@ TEST_CASE("TailFile finds and finishes the renamed file and continues with the n
   std::this_thread::sleep_for(std::chrono::milliseconds(100));
 
   appendTempFile(log_dir, "test.log", "line seven\n");
-  renameTempFile(log_dir, "test.1", "test.2");
-  renameTempFile(log_dir, "test.log", "test.1");
+  std::filesystem::rename(log_dir / "test.1", log_dir / "test.2");
+  std::filesystem::rename(log_dir / "test.log", log_dir / "test.1");
   createTempFile(log_dir, "test.log", "line eight is the last line\n");
 
   {
     auto test_plan = testController.createPlan(configuration, state_dir);
     auto tail_file = test_plan->addProcessor("TailFile", tail_file_uuid, "Tail", {success_relationship});
-    test_plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), test_file);
+    test_plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), test_file.string());
     auto log_attr = test_plan->addProcessor("LogAttribute", "Log", success_relationship, true);
     test_plan->setProperty(log_attr, minifi::processors::LogAttribute::FlowFilesToLog.getName(), "0");
     test_plan->setProperty(log_attr, minifi::processors::LogAttribute::LogPayload.getName(), "true");
@@ -1165,7 +1141,7 @@ TEST_CASE("TailFile yields if no work is done", "[yield]") {
   plan->setProperty(tail_file, minifi::processors::TailFile::Delimiter.getName(), "\n");
   plan->setProperty(tail_file, minifi::processors::TailFile::TailMode.getName(), "Multiple file");
   plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), ".*\\.log");
-  plan->setProperty(tail_file, minifi::processors::TailFile::BaseDirectory.getName(), temp_directory);
+  plan->setProperty(tail_file, minifi::processors::TailFile::BaseDirectory.getName(), temp_directory.string());
   plan->setProperty(tail_file, minifi::processors::TailFile::LookupFrequency.getName(), "0 sec");
 
   SECTION("Empty log file => yield") {
@@ -1238,7 +1214,7 @@ TEST_CASE("TailFile yields if no work is done on any files", "[yield][multiple_f
   plan->setProperty(tail_file, minifi::processors::TailFile::Delimiter.getName(), "\n");
   plan->setProperty(tail_file, minifi::processors::TailFile::TailMode.getName(), "Multiple file");
   plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), ".*\\.log");
-  plan->setProperty(tail_file, minifi::processors::TailFile::BaseDirectory.getName(), temp_directory);
+  plan->setProperty(tail_file, minifi::processors::TailFile::BaseDirectory.getName(), temp_directory.string());
   plan->setProperty(tail_file, minifi::processors::TailFile::LookupFrequency.getName(), "0 sec");
 
   createTempFile(temp_directory, "first.log", "stuff\n");
@@ -1290,13 +1266,13 @@ TEST_CASE("TailFile doesn't yield if work was done on rotated files only", "[yie
   LogTestController::getInstance().setTrace<minifi::processors::LogAttribute>();
 
   auto temp_directory = testController.createTempDirectory();
-  std::string full_file_name = createTempFile(temp_directory, "test.log", "stuff\n");
+  auto full_file_name = createTempFile(temp_directory, "test.log", "stuff\n");
 
   auto plan = testController.createPlan();
 
   auto tail_file = plan->addProcessor("TailFile", "Tail");
   plan->setProperty(tail_file, minifi::processors::TailFile::Delimiter.getName(), "\n");
-  plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), full_file_name);
+  plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), full_file_name.string());
 
   testController.runSession(plan, true);
 
@@ -1306,7 +1282,7 @@ TEST_CASE("TailFile doesn't yield if work was done on rotated files only", "[yie
   std::this_thread::sleep_for(std::chrono::milliseconds(100));
 
   SECTION("File rotated but not written => yield") {
-    renameTempFile(temp_directory, "test.log", "test.1");
+    std::filesystem::rename(temp_directory / "test.log", temp_directory / "test.1");
 
     SECTION("Don't create empty new log file") {
     }
@@ -1324,7 +1300,7 @@ TEST_CASE("TailFile doesn't yield if work was done on rotated files only", "[yie
       appendTempFile(temp_directory, "test.log", "more stuff\n");
     }
 
-    renameTempFile(temp_directory, "test.log", "test.1");
+    std::filesystem::rename(temp_directory / "test.log", temp_directory / "test.1");
 
     SECTION("New content after rotation") {
       createTempFile(temp_directory, "test.log", "even more stuff\n");
@@ -1351,13 +1327,13 @@ TEST_CASE("TailFile handles the Delimiter setting correctly", "[delimiter]") {
     auto temp_directory = testController.createTempDirectory();
 
     std::string delimiter = test_case.second;
-    std::string full_file_name = createTempFile(temp_directory, "test.log", utils::StringUtils::join_pack("one", delimiter, "two", delimiter));
+    auto full_file_name = createTempFile(temp_directory, "test.log", utils::StringUtils::join_pack("one", delimiter, "two", delimiter));
 
     auto plan = testController.createPlan();
 
     auto tail_file = plan->addProcessor("TailFile", "Tail");
     plan->setProperty(tail_file, minifi::processors::TailFile::Delimiter.getName(), test_case.first);
-    plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), full_file_name);
+    plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), full_file_name.string());
 
     auto log_attribute = plan->addProcessor("LogAttribute", "Log", core::Relationship("success", "description"), true);
     plan->setProperty(log_attribute, minifi::processors::LogAttribute::FlowFilesToLog.getName(), "0");
@@ -1383,12 +1359,12 @@ TEST_CASE("TailFile handles Unix/Windows line endings correctly", "[simple]") {
   LogTestController::getInstance().setTrace<minifi::processors::LogAttribute>();
 
   auto temp_directory = testController.createTempDirectory();
-  std::string full_file_name = createTempFile(temp_directory, "test.log", "line1\nline two\n", std::ios::out);  // write in text mode
+  auto full_file_name = createTempFile(temp_directory, "test.log", "line1\nline two\n", std::ios::out);  // write in text mode
 
   auto plan = testController.createPlan();
 
   auto tail_file = plan->addProcessor("TailFile", "Tail");
-  plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), full_file_name);
+  plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), full_file_name.string());
 
   auto log_attribute = plan->addProcessor("LogAttribute", "Log", core::Relationship("success", "description"), true);
   plan->setProperty(log_attribute, minifi::processors::LogAttribute::FlowFilesToLog.getName(), "0");
@@ -1412,12 +1388,12 @@ TEST_CASE("TailFile can tail all files in a directory recursively", "[multiple]"
   LogTestController::getInstance().setTrace<minifi::processors::TailFile>();
   LogTestController::getInstance().setTrace<minifi::processors::LogAttribute>();
 
-  std::string base_directory = testController.createTempDirectory();
-  std::string directory1 = base_directory + utils::file::FileUtils::get_separator() + "one";
+  auto base_directory = testController.createTempDirectory();
+  auto directory1 = base_directory / "one";
   utils::file::FileUtils::create_dir(directory1);
-  std::string directory11 = directory1 + utils::file::FileUtils::get_separator() + "one_child";
+  auto directory11 = directory1 / "one_child";
   utils::file::FileUtils::create_dir(directory11);
-  std::string directory2 = base_directory + utils::file::FileUtils::get_separator() + "two";
+  auto directory2 = base_directory / "two";
   utils::file::FileUtils::create_dir(directory2);
 
   createTempFile(base_directory, "test.orange.log", "orange juice\n");
@@ -1430,7 +1406,7 @@ TEST_CASE("TailFile can tail all files in a directory recursively", "[multiple]"
 
   auto tail_file = plan->addProcessor("TailFile", "Tail");
   plan->setProperty(tail_file, minifi::processors::TailFile::TailMode.getName(), "Multiple file");
-  plan->setProperty(tail_file, minifi::processors::TailFile::BaseDirectory.getName(), base_directory);
+  plan->setProperty(tail_file, minifi::processors::TailFile::BaseDirectory.getName(), base_directory.string());
   plan->setProperty(tail_file, minifi::processors::TailFile::LookupFrequency.getName(), "0 sec");
   plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), ".*\\.log");
 
@@ -1463,14 +1439,14 @@ TEST_CASE("TailFile interprets the lookup frequency property correctly", "[multi
   LogTestController::getInstance().setTrace<minifi::processors::TailFile>();
   LogTestController::getInstance().setTrace<minifi::processors::LogAttribute>();
 
-  std::string directory = testController.createTempDirectory();
+  auto directory = testController.createTempDirectory();
   createTempFile(directory, "test.red.log", "cherry\n");
 
   auto plan = testController.createPlan();
 
   auto tail_file = plan->addProcessor("TailFile", "Tail");
   plan->setProperty(tail_file, minifi::processors::TailFile::TailMode.getName(), "Multiple file");
-  plan->setProperty(tail_file, minifi::processors::TailFile::BaseDirectory.getName(), directory);
+  plan->setProperty(tail_file, minifi::processors::TailFile::BaseDirectory.getName(), directory.string());
   plan->setProperty(tail_file, minifi::processors::TailFile::FileName.getName(), ".*\\.log");
 
   auto log_attribute = plan->addProcessor("LogAttribute", "Log", core::Relationship("success", "description"), true);
@@ -1543,7 +1519,7 @@ TEST_CASE("TailFile reads from a single file when Initial Start Position is set"
   auto temp_file_path = createTempFile(dir, TMP_FILE, NEWLINE_FILE);
 
   plan->setProperty(logattribute, org::apache::nifi::minifi::processors::LogAttribute::FlowFilesToLog.getName(), "0");
-  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file_path);
+  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file_path.string());
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::Delimiter.getName(), "\n");
 
   SECTION("Initial Start Position is set to Beginning of File") {
@@ -1619,7 +1595,7 @@ TEST_CASE("TailFile reads from a single file when Initial Start Position is set
   auto temp_file_path = createTempFile(dir, TMP_FILE, NEWLINE_FILE);
 
   plan->setProperty(logattribute, org::apache::nifi::minifi::processors::LogAttribute::FlowFilesToLog.getName(), "0");
-  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file_path);
+  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file_path.string());
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::Delimiter.getName(), "\n");
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::InitialStartPosition.getName(), "Current Time");
 
@@ -1633,7 +1609,7 @@ TEST_CASE("TailFile reads from a single file when Initial Start Position is set
   const std::string DATA_IN_NEW_FILE = "data in new file\n";
   std::this_thread::sleep_for(std::chrono::milliseconds(100));  // make sure the new file gets newer modification time
   appendTempFile(dir, TMP_FILE, NEW_TAIL_DATA);
-  renameTempFile(dir, TMP_FILE, ROLLED_OVER_TMP_FILE);
+  std::filesystem::rename(dir / TMP_FILE, dir / ROLLED_OVER_TMP_FILE);
   createTempFile(dir, TMP_FILE, DATA_IN_NEW_FILE);
 
   testController.runSession(plan);
@@ -1664,7 +1640,7 @@ TEST_CASE("TailFile reads multiple files when Initial Start Position is set", "[
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), ".*\\.txt");
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::Delimiter.getName(), "\n");
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::TailMode.getName(), "Multiple file");
-  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::BaseDirectory.getName(), dir);
+  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::BaseDirectory.getName(), dir.string());
 
   SECTION("Initial Start Position is set to Beginning of File") {
     plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::InitialStartPosition.getName(), "Beginning of File");
@@ -1748,7 +1724,7 @@ TEST_CASE("Initial Start Position is set to invalid or empty value", "[initialSt
   auto temp_file_path = createTempFile(dir, TMP_FILE, NEWLINE_FILE);
 
   plan->setProperty(logattribute, org::apache::nifi::minifi::processors::LogAttribute::FlowFilesToLog.getName(), "0");
-  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file_path);
+  plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::FileName.getName(), temp_file_path.string());
   plan->setProperty(tailfile, org::apache::nifi::minifi::processors::TailFile::Delimiter.getName(), "\n");
 
   SECTION("Initial Start Position is empty") {
@@ -1855,21 +1831,16 @@ TEST_CASE("TailFile honors batch size for maximum lines processed", "[batchSize]
   auto tailfile = std::make_shared<minifi::processors::TailFile>("TailFile");
   minifi::test::SingleProcessorTestController test_controller(tailfile);
 
-  auto dir = test_controller.createTempDirectory();
-  std::stringstream temp_file;
-  temp_file << dir << utils::file::get_separator() << TMP_FILE;
+  auto temp_file_path  = test_controller.createTempDirectory() / TMP_FILE;
 
   std::ofstream tmpfile;
-  tmpfile.open(temp_file.str(), std::ios::out | std::ios::binary);
+  tmpfile.open(temp_file_path, std::ios::out | std::ios::binary);
   for (auto i = 0; i < 20; ++i) {
     tmpfile << NEW_TAIL_DATA;
   }
   tmpfile.close();
 
-  std::stringstream state_file;
-  state_file << dir << utils::file::get_separator() << STATE_FILE;
-
-  tailfile->setProperty(minifi::processors::TailFile::FileName.getName(), temp_file.str());
+  tailfile->setProperty(minifi::processors::TailFile::FileName.getName(), temp_file_path.string());
   tailfile->setProperty(minifi::processors::TailFile::Delimiter.getName(), "\n");
   tailfile->setProperty(minifi::processors::TailFile::BatchSize.getName(), "10");
 
diff --git a/extensions/windows-event-log/Bookmark.cpp b/extensions/windows-event-log/Bookmark.cpp
index 600ef3a13..aa403d3c0 100644
--- a/extensions/windows-event-log/Bookmark.cpp
+++ b/extensions/windows-event-log/Bookmark.cpp
@@ -36,7 +36,7 @@ static const std::string BOOKMARK_KEY = "bookmark";
 
 Bookmark::Bookmark(const std::wstring& channel,
     const std::wstring& query,
-    const std::string& bookmarkRootDir,
+    const std::filesystem::path& bookmarkRootDir,
     const utils::Identifier& uuid,
     bool processOldEvents,
     core::CoreComponentStateManager* state_manager,
@@ -47,17 +47,17 @@ Bookmark::Bookmark(const std::wstring& channel,
   if (state_manager_->get(state_map) && state_map.count(BOOKMARK_KEY) == 1U) {
     bookmarkXml_ = wel::to_wstring(state_map[BOOKMARK_KEY].c_str());
   } else if (!bookmarkRootDir.empty()) {
-    filePath_ = utils::file::concat_path(
-      utils::file::concat_path(
-        utils::file::concat_path(bookmarkRootDir, "uuid"), uuid.to_string()), "Bookmark.txt");
+    filePath_ = bookmarkRootDir / "uuid" / uuid.to_string().view() / "Bookmark.txt";
 
     std::wstring bookmarkXml;
     if (getBookmarkXmlFromFile(bookmarkXml)) {
       if (saveBookmarkXml(bookmarkXml) && state_manager_->persist()) {
         logger_->log_info("State migration successful");
-        rename(filePath_.c_str(), (filePath_ + "-migrated").c_str());
+        auto migrated_path = filePath_;
+        migrated_path += "-migrated";
+        std::filesystem::rename(filePath_, migrated_path);
       } else {
-        logger_->log_warn("Could not migrate state from specified State Directory %s", bookmarkRootDir);
+        logger_->log_warn("Could not migrate state from specified State Directory %s", bookmarkRootDir.string());
       }
     }
   } else {
diff --git a/extensions/windows-event-log/Bookmark.h b/extensions/windows-event-log/Bookmark.h
index 762710df8..60d6860b8 100644
--- a/extensions/windows-event-log/Bookmark.h
+++ b/extensions/windows-event-log/Bookmark.h
@@ -41,7 +41,7 @@ class Bookmark {
  public:
   Bookmark(const std::wstring& channel,
       const std::wstring& query,
-      const std::string& bookmarkRootDir,
+      const std::filesystem::path& bookmarkRootDir,
       const utils::Identifier& uuid,
       bool processOldEvents,
       core::CoreComponentStateManager* state_manager,
@@ -61,7 +61,7 @@ class Bookmark {
 
   std::shared_ptr<core::logging::Logger> logger_;
   core::CoreComponentStateManager* state_manager_;
-  std::string filePath_;
+  std::filesystem::path filePath_;
   bool ok_{};
   unique_evt_handle hBookmark_;
   std::wstring bookmarkXml_;
diff --git a/extensions/windows-event-log/tests/CWELTestUtils.h b/extensions/windows-event-log/tests/CWELTestUtils.h
index a15bb59b8..f1bd1a597 100644
--- a/extensions/windows-event-log/tests/CWELTestUtils.h
+++ b/extensions/windows-event-log/tests/CWELTestUtils.h
@@ -60,7 +60,7 @@ class OutputFormatTestController : public TestController {
     auto dir = createTempDirectory();
 
     auto put_file = test_plan->addProcessor("PutFile", "putFile", Success, true);
-    test_plan->setProperty(put_file, PutFile::Directory.getName(), dir);
+    test_plan->setProperty(put_file, PutFile::Directory.getName(), dir.string());
 
     {
       dispatchBookmarkEvent();
@@ -80,7 +80,7 @@ class OutputFormatTestController : public TestController {
       auto files = utils::file::list_dir_all(dir, LogTestController::getInstance().getLogger<LogTestController>(), false);
       REQUIRE(files.size() == 1);
 
-      std::ifstream file{utils::file::concat_path(files[0].first, files[0].second)};
+      std::ifstream file{files[0].first / files[0].second};
       return {std::istreambuf_iterator<char>{file}, {}};
     }
   }
diff --git a/libminifi/include/Defaults.h b/libminifi/include/Defaults.h
index f1efd52d5..0ee8eaa48 100644
--- a/libminifi/include/Defaults.h
+++ b/libminifi/include/Defaults.h
@@ -17,16 +17,8 @@
 
 #pragma once
 
-#ifdef WIN32
-#define DEFAULT_NIFI_CONFIG_YML "\\conf\\config.yml"
-#define DEFAULT_NIFI_PROPERTIES_FILE "\\conf\\minifi.properties"
-#define DEFAULT_LOG_PROPERTIES_FILE "\\conf\\minifi-log.properties"
-#define DEFAULT_UID_PROPERTIES_FILE "\\conf\\minifi-uid.properties"
-#define DEFAULT_BOOTSTRAP_FILE "\\conf\\bootstrap.conf"
-#else
-#define DEFAULT_NIFI_CONFIG_YML "./conf/config.yml"
-#define DEFAULT_NIFI_PROPERTIES_FILE "./conf/minifi.properties"
-#define DEFAULT_LOG_PROPERTIES_FILE "./conf/minifi-log.properties"
-#define DEFAULT_UID_PROPERTIES_FILE "./conf/minifi-uid.properties"
-#define DEFAULT_BOOTSTRAP_FILE "./conf/bootstrap.conf"
-#endif
+const std::filesystem::path DEFAULT_NIFI_CONFIG_YML = std::filesystem::path("conf") / "config.yml";
+const std::filesystem::path DEFAULT_NIFI_PROPERTIES_FILE = std::filesystem::path("conf") / "minifi.properties";
+const std::filesystem::path DEFAULT_LOG_PROPERTIES_FILE = std::filesystem::path("conf") / "minifi-log.properties";
+const std::filesystem::path DEFAULT_UID_PROPERTIES_FILE = std::filesystem::path("conf") / "minifi-uid.properties";
+const std::filesystem::path DEFAULT_BOOTSTRAP_FILE = std::filesystem::path("conf") / "bootstrap.conf";
diff --git a/libminifi/include/controllers/SSLContextService.h b/libminifi/include/controllers/SSLContextService.h
index e2f09b843..19a492574 100644
--- a/libminifi/include/controllers/SSLContextService.h
+++ b/libminifi/include/controllers/SSLContextService.h
@@ -82,14 +82,12 @@ class SSLContextService : public core::controller::ControllerService {
   explicit SSLContextService(std::string name, const utils::Identifier &uuid = {})
       : ControllerService(std::move(name), uuid),
         initialized_(false),
-        valid_(false),
         logger_(core::logging::LoggerFactory<SSLContextService>::getLogger()) {
   }
 
   explicit SSLContextService(std::string name, const std::shared_ptr<Configure> &configuration)
       : ControllerService(std::move(name)),
         initialized_(false),
-        valid_(false),
         logger_(core::logging::LoggerFactory<SSLContextService>::getLogger()) {
     setConfiguration(configuration);
     initialize();
@@ -143,15 +141,13 @@ class SSLContextService : public core::controller::ControllerService {
 
   std::unique_ptr<SSLContext> createSSLContext();
 
-  const std::string &getCertificateFile();
+  const std::filesystem::path& getCertificateFile();
 
-  const std::string &getPassphrase();
+  const std::string& getPassphrase();
 
-  const std::string &getPassphraseFile();
+  const std::filesystem::path& getPrivateKeyFile();
 
-  const std::string &getPrivateKeyFile();
-
-  const std::string &getCACertificate();
+  const std::filesystem::path& getCACertificate();
 
   void yield() override {
   }
@@ -209,12 +205,10 @@ class SSLContextService : public core::controller::ControllerService {
 
   std::mutex initialization_mutex_;
   bool initialized_;
-  std::atomic<bool> valid_;
-  std::string certificate_;
-  std::string private_key_;
+  std::filesystem::path certificate_;
+  std::filesystem::path private_key_;
   std::string passphrase_;
-  std::string passphrase_file_;
-  std::string ca_certificate_;
+  std::filesystem::path ca_certificate_;
   bool use_system_cert_store_ = false;
 #ifdef WIN32
   std::string cert_store_location_;
@@ -236,8 +230,8 @@ class SSLContextService : public core::controller::ControllerService {
   }
 #endif
 
-  static bool isFileTypeP12(const std::string& filename) {
-    return utils::StringUtils::endsWith(filename, "p12", false);
+  static bool isFileTypeP12(const std::filesystem::path& filename) {
+    return utils::StringUtils::endsWith(filename.string(), "p12", false);
   }
 
  private:
diff --git a/libminifi/include/core/ConfigurableComponent.h b/libminifi/include/core/ConfigurableComponent.h
index d243bf6ec..b909ca71c 100644
--- a/libminifi/include/core/ConfigurableComponent.h
+++ b/libminifi/include/core/ConfigurableComponent.h
@@ -59,6 +59,18 @@ class ConfigurableComponent {
   template<typename T>
   bool getProperty(const std::string name, T &value) const;
 
+  template<typename T = std::string>
+  std::enable_if_t<std::is_default_constructible<T>::value, std::optional<T>>
+  getProperty(const std::string& property_name) const {
+    T value;
+    try {
+      if (!getProperty(property_name, value)) return std::nullopt;
+    } catch (const utils::internal::ValueException&) {
+      return std::nullopt;
+    }
+    return value;
+  }
+
   /**
    * Provides a reference for the property.
    */
diff --git a/libminifi/include/core/ContentRepository.h b/libminifi/include/core/ContentRepository.h
index e963d99f5..b53f66c9b 100644
--- a/libminifi/include/core/ContentRepository.h
+++ b/libminifi/include/core/ContentRepository.h
@@ -15,8 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#ifndef LIBMINIFI_INCLUDE_CORE_CONTENTREPOSITORY_H_
-#define LIBMINIFI_INCLUDE_CORE_CONTENTREPOSITORY_H_
+#pragma once
 
 #include <map>
 #include <memory>
@@ -30,11 +29,7 @@
 #include "ContentSession.h"
 #include "utils/GeneralUtils.h"
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace core {
+namespace org::apache::nifi::minifi::core {
 
 /**
  * Content repository definition that extends StreamManager.
@@ -68,10 +63,4 @@ class ContentRepository : public StreamManager<minifi::ResourceClaim>, public ut
   std::map<std::string, uint32_t> count_map_;
 };
 
-}  // namespace core
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
-
-#endif  // LIBMINIFI_INCLUDE_CORE_CONTENTREPOSITORY_H_
+}  // namespace org::apache::nifi::minifi::core
diff --git a/libminifi/include/core/FlowConfiguration.h b/libminifi/include/core/FlowConfiguration.h
index 860351cb6..36f111d50 100644
--- a/libminifi/include/core/FlowConfiguration.h
+++ b/libminifi/include/core/FlowConfiguration.h
@@ -15,8 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#ifndef LIBMINIFI_INCLUDE_CORE_FLOWCONFIGURATION_H_
-#define LIBMINIFI_INCLUDE_CORE_FLOWCONFIGURATION_H_
+#pragma once
 
 #include <memory>
 #include <optional>
@@ -42,11 +41,7 @@
 #include "utils/file/FileSystem.h"
 #include "utils/ChecksumCalculator.h"
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace core {
+namespace org::apache::nifi::minifi::core {
 
 class static_initializers {
  public:
@@ -68,7 +63,7 @@ class FlowConfiguration : public CoreComponent {
    */
   explicit FlowConfiguration(const std::shared_ptr<core::Repository>& repo, std::shared_ptr<core::Repository> flow_file_repo,
                              std::shared_ptr<core::ContentRepository> content_repo, std::shared_ptr<io::StreamFactory> stream_factory,
-                             const std::shared_ptr<Configure>& configuration, const std::optional<std::string>& path,
+                             std::shared_ptr<Configure> configuration, const std::optional<std::filesystem::path>& path,
                              std::shared_ptr<utils::file::FileSystem> filesystem = std::make_shared<utils::file::FileSystem>());
 
   ~FlowConfiguration() override;
@@ -86,11 +81,11 @@ class FlowConfiguration : public CoreComponent {
       const utils::Identifier &uuid);
 
   // Create Connection
-  std::unique_ptr<minifi::Connection> createConnection(const std::string &name, const utils::Identifier &uuid) const;
+  [[nodiscard]] std::unique_ptr<minifi::Connection> createConnection(const std::string &name, const utils::Identifier &uuid) const;
   // Create Provenance Report Task
   std::unique_ptr<core::reporting::SiteToSiteProvenanceReportingTask> createProvenanceReportTask();
 
-  std::shared_ptr<state::response::FlowVersion> getFlowVersion() const {
+  [[nodiscard]] std::shared_ptr<state::response::FlowVersion> getFlowVersion() const {
     return flow_version_;
   }
 
@@ -104,7 +99,7 @@ class FlowConfiguration : public CoreComponent {
    * Returns the configuration path string
    * @return config_path_
    */
-  const std::optional<std::string> &getConfigurationPath() {
+  const std::optional<std::filesystem::path>& getConfigurationPath() {
     return config_path_;
   }
 
@@ -130,7 +125,7 @@ class FlowConfiguration : public CoreComponent {
   // based, shared controller service map.
   std::shared_ptr<core::controller::ControllerServiceMap> controller_services_;
   // configuration path
-  std::optional<std::string> config_path_;
+  std::optional<std::filesystem::path> config_path_;
   // flow file repo
   std::shared_ptr<core::Repository> flow_file_repo_;
   // content repository.
@@ -146,11 +141,4 @@ class FlowConfiguration : public CoreComponent {
   std::shared_ptr<logging::Logger> logger_;
 };
 
-}  // namespace core
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
-
-#endif  // LIBMINIFI_INCLUDE_CORE_FLOWCONFIGURATION_H_
-
+}  // namespace org::apache::nifi::minifi::core
diff --git a/libminifi/include/core/ProcessSessionReadCallback.h b/libminifi/include/core/ProcessSessionReadCallback.h
index e4bf7d74d..55389ac45 100644
--- a/libminifi/include/core/ProcessSessionReadCallback.h
+++ b/libminifi/include/core/ProcessSessionReadCallback.h
@@ -29,16 +29,16 @@
 namespace org::apache::nifi::minifi::core {
 class ProcessSessionReadCallback {
  public:
-  ProcessSessionReadCallback(const std::string &tmpFile, std::string destFile, std::shared_ptr<logging::Logger> logger);
+  ProcessSessionReadCallback(std::filesystem::path temp_file, std::filesystem::path dest_file, std::shared_ptr<logging::Logger> logger);
   ~ProcessSessionReadCallback();
   int64_t operator()(const std::shared_ptr<io::InputStream>& stream);
   bool commit();
 
  private:
   std::shared_ptr<logging::Logger> logger_;
-  std::ofstream _tmpFileOs;
-  bool _writeSucceeded = false;
-  std::string _tmpFile;
-  std::string _destFile;
+  std::ofstream tmp_file_os_;
+  bool write_succeeded_ = false;
+  std::filesystem::path tmp_file_;
+  std::filesystem::path dest_file_;
 };
 }  // namespace org::apache::nifi::minifi::core
diff --git a/libminifi/include/core/extension/Utils.h b/libminifi/include/core/extension/Utils.h
index 53d915538..3b6e5b8ce 100644
--- a/libminifi/include/core/extension/Utils.h
+++ b/libminifi/include/core/extension/Utils.h
@@ -85,15 +85,13 @@ std::optional<LibraryDescriptor> asDynamicLibrary(const std::filesystem::path& p
 #else
   static const std::string_view prefix = "lib";
 #endif
-  std::string filepath;
-  std::string filename;
-  utils::file::getFileNameAndPath(path.string(), filepath, filename);
-  if (!utils::StringUtils::startsWith(filename, prefix) || !utils::StringUtils::endsWith(filename, extension)) {
+  if (!path.filename().string().starts_with(prefix) || path.filename().extension().string() != extension) {
     return {};
   }
+  std::string filename = path.filename().string();
   return LibraryDescriptor{
       filename.substr(prefix.length(), filename.length() - extension.length() - prefix.length()),
-      filepath,
+      path.parent_path(),
       filename
   };
 }
diff --git a/libminifi/include/core/logging/LoggerConfiguration.h b/libminifi/include/core/logging/LoggerConfiguration.h
index b9c9197a5..eb79f9977 100644
--- a/libminifi/include/core/logging/LoggerConfiguration.h
+++ b/libminifi/include/core/logging/LoggerConfiguration.h
@@ -21,6 +21,7 @@
 #pragma once
 
 #include <memory>
+#include <utility>
 #include <vector>
 #include <map>
 #include <mutex>
@@ -49,8 +50,8 @@ namespace org::apache::nifi::minifi::core::logging {
 
 namespace internal {
 struct LoggerNamespace {
-  spdlog::level::level_enum level;
-  bool has_level;
+  spdlog::level::level_enum level{spdlog::level::off};
+  bool has_level{false};
   std::vector<std::shared_ptr<spdlog::sinks::sink>> sinks;
   // sinks made available to all descendants
   std::vector<std::shared_ptr<spdlog::sinks::sink>> exported_sinks;
@@ -59,9 +60,7 @@ struct LoggerNamespace {
   void forEachSink(const std::function<void(const std::shared_ptr<spdlog::sinks::sink>&)>& op) const;
 
   LoggerNamespace()
-      : level(spdlog::level::off),
-        has_level(false),
-        sinks(std::vector<std::shared_ptr<spdlog::sinks::sink>>()),
+      : sinks(std::vector<std::shared_ptr<spdlog::sinks::sink>>()),
         children(std::map<std::string, std::shared_ptr<LoggerNamespace>>()) {
   }
 };
@@ -117,8 +116,8 @@ class LoggerConfiguration {
 
  protected:
   static std::shared_ptr<internal::LoggerNamespace> initialize_namespaces(const std::shared_ptr<LoggerProperties> &logger_properties, const std::shared_ptr<Logger> &logger = {});
-  static std::shared_ptr<spdlog::logger> get_logger(const std::shared_ptr<Logger> &logger, const std::shared_ptr<internal::LoggerNamespace> &root_namespace, const std::string &name,
-                                                    const std::shared_ptr<spdlog::formatter> &formatter, bool remove_if_present = false);
+  static std::shared_ptr<spdlog::logger> get_logger(const std::shared_ptr<Logger>& logger, const std::shared_ptr<internal::LoggerNamespace> &root_namespace, const std::string &name,
+                                                    const std::shared_ptr<spdlog::formatter>& formatter, bool remove_if_present = false);
 
  private:
   std::shared_ptr<Logger> getLogger(const std::string& name, const std::lock_guard<std::mutex>& lock);
@@ -134,14 +133,14 @@ class LoggerConfiguration {
 
   class LoggerImpl : public Logger {
    public:
-    explicit LoggerImpl(const std::string &name, const std::shared_ptr<LoggerControl> &controller, const std::shared_ptr<spdlog::logger> &delegate)
+    explicit LoggerImpl(std::string name, const std::shared_ptr<LoggerControl> &controller, const std::shared_ptr<spdlog::logger> &delegate)
         : Logger(delegate, controller),
-          name(name) {
+          name(std::move(name)) {
     }
 
     void set_delegate(std::shared_ptr<spdlog::logger> delegate) {
       std::lock_guard<std::mutex> lock(mutex_);
-      delegate_ = delegate;
+      delegate_ = std::move(delegate);
     }
     const std::string name;
   };
diff --git a/libminifi/include/core/yaml/YamlConfiguration.h b/libminifi/include/core/yaml/YamlConfiguration.h
index bf7f280ae..a8b7ab5c1 100644
--- a/libminifi/include/core/yaml/YamlConfiguration.h
+++ b/libminifi/include/core/yaml/YamlConfiguration.h
@@ -59,7 +59,7 @@ class YamlConfiguration : public FlowConfiguration {
  public:
   explicit YamlConfiguration(const std::shared_ptr<core::Repository>& repo, const std::shared_ptr<core::Repository>& flow_file_repo,
                              const std::shared_ptr<core::ContentRepository>& content_repo, const std::shared_ptr<io::StreamFactory>& stream_factory,
-                             const std::shared_ptr<Configure>& configuration, const std::optional<std::string>& path = {},
+                             const std::shared_ptr<Configure>& configuration, const std::optional<std::filesystem::path>& path = {},
                              const std::shared_ptr<utils::file::FileSystem>& filesystem = std::make_shared<utils::file::FileSystem>());
 
   ~YamlConfiguration() override = default;
diff --git a/libminifi/include/io/FileStream.h b/libminifi/include/io/FileStream.h
index 754d3ab59..e30ee0836 100644
--- a/libminifi/include/io/FileStream.h
+++ b/libminifi/include/io/FileStream.h
@@ -15,8 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#ifndef LIBMINIFI_INCLUDE_IO_FILESTREAM_H_
-#define LIBMINIFI_INCLUDE_IO_FILESTREAM_H_
+#pragma once
 
 #include <memory>
 #include <vector>
@@ -25,11 +24,7 @@
 #include "BaseStream.h"
 #include "core/logging/LoggerFactory.h"
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace io {
+namespace org::apache::nifi::minifi::io {
 
 /**
  * Purpose: File Stream Base stream extension. This is intended to be a thread safe access to
@@ -44,7 +39,7 @@ class FileStream : public io::BaseStream {
    * File Stream constructor that accepts an fstream shared pointer.
    * It must already be initialized for read and write.
    */
-  explicit FileStream(const std::string &path, uint32_t offset, bool write_enable = false);
+  explicit FileStream(std::filesystem::path path, uint32_t offset, bool write_enable = false);
 
   /**
    * File Stream constructor that accepts an fstream shared pointer.
@@ -52,7 +47,7 @@ class FileStream : public io::BaseStream {
    * @param path path to file
    * @param append identifies if this is an append or overwriting the file
    */
-  explicit FileStream(const std::string &path, bool append = false);
+  explicit FileStream(std::filesystem::path path, bool append = false);
 
   ~FileStream() override {
     close();
@@ -65,9 +60,9 @@ class FileStream : public io::BaseStream {
    */
   void seek(size_t offset) override;
 
-  size_t tell() const override;
+  [[nodiscard]] size_t tell() const override;
 
-  size_t size() const override {
+  [[nodiscard]] size_t size() const override {
     return length_;
   }
 
@@ -94,16 +89,10 @@ class FileStream : public io::BaseStream {
   std::mutex file_lock_;
   std::unique_ptr<std::fstream> file_stream_;
   size_t offset_;
-  std::string path_;
+  std::filesystem::path path_;
   size_t length_;
 
   std::shared_ptr<core::logging::Logger> logger_ = core::logging::LoggerFactory<FileStream>::getLogger();
 };
 
-}  // namespace io
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
-
-#endif  // LIBMINIFI_INCLUDE_IO_FILESTREAM_H_
+}  // namespace org::apache::nifi::minifi::io
diff --git a/libminifi/include/properties/Decryptor.h b/libminifi/include/properties/Decryptor.h
index a8ca4bceb..359e20738 100644
--- a/libminifi/include/properties/Decryptor.h
+++ b/libminifi/include/properties/Decryptor.h
@@ -41,7 +41,7 @@ class Decryptor {
     return provider_.decrypt(encrypted_text);
   }
 
-  static std::optional<Decryptor> create(const std::string& minifi_home) {
+  static std::optional<Decryptor> create(const std::filesystem::path& minifi_home) {
     return utils::crypto::EncryptionProvider::create(minifi_home)
         | utils::map([](const utils::crypto::EncryptionProvider& provider) {return Decryptor{provider};});
   }
diff --git a/libminifi/include/properties/Properties.h b/libminifi/include/properties/Properties.h
index ca227d145..8979f649e 100644
--- a/libminifi/include/properties/Properties.h
+++ b/libminifi/include/properties/Properties.h
@@ -17,8 +17,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#ifndef LIBMINIFI_INCLUDE_PROPERTIES_PROPERTIES_H_
-#define LIBMINIFI_INCLUDE_PROPERTIES_PROPERTIES_H_
+#pragma once
 
 #include <map>
 #include <memory>
@@ -31,10 +30,7 @@
 #include "utils/ChecksumCalculator.h"
 #include "utils/StringUtils.h"
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
+namespace org::apache::nifi::minifi {
 
 enum class PropertyChangeLifetime {
   TRANSIENT,  // the changed value will not be committed to disk
@@ -78,7 +74,7 @@ class Properties {
         it->second.need_to_persist_new_value = true;
       }
     } else {
-      // brand new property
+      // brand-new property
       properties_[key] = PropertyValue{value, active_value, should_persist};
     }
 
@@ -117,10 +113,10 @@ class Properties {
    * Load configure file
    * @param fileName path of the configuration file RELATIVE to MINIFI_HOME set by setHome()
    */
-  void loadConfigureFile(const char *fileName);
+  void loadConfigureFile(const std::filesystem::path& configuration_file);
 
   // Set the determined MINIFI_HOME
-  void setHome(std::string minifiHome) {
+  void setHome(std::filesystem::path minifiHome) {
     minifi_home_ = std::move(minifiHome);
   }
 
@@ -133,7 +129,7 @@ class Properties {
   }
 
   // Get the determined MINIFI_HOME
-  std::string getHome() const {
+  std::filesystem::path getHome() const {
     return minifi_home_;
   }
 
@@ -141,7 +137,7 @@ class Properties {
 
   utils::ChecksumCalculator& getChecksumCalculator() { return checksum_calculator_; }
 
-  std::string getFilePath() const;
+  std::filesystem::path getFilePath() const;
 
  protected:
   std::map<std::string, std::string> getProperties() const;
@@ -151,7 +147,7 @@ class Properties {
 
   bool dirty_{false};
 
-  std::string properties_file_;
+  std::filesystem::path properties_file_;
 
   utils::ChecksumCalculator checksum_calculator_;
 
@@ -160,13 +156,9 @@ class Properties {
   // Logger
   std::shared_ptr<core::logging::Logger> logger_;
   // Home location for this executable
-  std::string minifi_home_;
+  std::filesystem::path minifi_home_;
 
   std::string name_;
 };
 
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
-#endif  // LIBMINIFI_INCLUDE_PROPERTIES_PROPERTIES_H_
+}  // namespace org::apache::nifi::minifi
diff --git a/libminifi/include/properties/PropertiesFile.h b/libminifi/include/properties/PropertiesFile.h
index a6c15a15e..b423b175d 100644
--- a/libminifi/include/properties/PropertiesFile.h
+++ b/libminifi/include/properties/PropertiesFile.h
@@ -16,6 +16,7 @@
  */
 #pragma once
 
+#include <filesystem>
 #include <istream>
 #include <optional>
 #include <string>
@@ -23,10 +24,7 @@
 
 #include "utils/crypto/EncryptionUtils.h"
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
+namespace org::apache::nifi::minifi {
 
 class PropertiesFile {
  public:
@@ -63,7 +61,7 @@ class PropertiesFile {
   void append(const std::string& key, const std::string& value);
   int erase(const std::string& key);
 
-  void writeTo(const std::string& file_path) const;
+  void writeTo(const std::filesystem::path& file_path) const;
 
   [[nodiscard]] size_t size() const { return lines_.size(); }
 
@@ -81,7 +79,4 @@ class PropertiesFile {
   Lines lines_;
 };
 
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
+}  // namespace org::apache::nifi::minifi
diff --git a/libminifi/include/utils/ChecksumCalculator.h b/libminifi/include/utils/ChecksumCalculator.h
index 99a47d102..8cc1a4ed4 100644
--- a/libminifi/include/utils/ChecksumCalculator.h
+++ b/libminifi/include/utils/ChecksumCalculator.h
@@ -17,31 +17,28 @@
 
 #pragma once
 
+#include <filesystem>
 #include <optional>
 #include <string>
 #include <utility>
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace utils {
+namespace org::apache::nifi::minifi::utils {
 
 class ChecksumCalculator {
  public:
   static constexpr const char* CHECKSUM_TYPE = "SHA256";
   static constexpr size_t LENGTH_OF_HASH_IN_BYTES = 32;
 
-  void setFileLocation(const std::string& file_location);
-  std::string getFileName() const;
+  void setFileLocation(const std::filesystem::path& file_location);
+  [[nodiscard]] std::filesystem::path getFileName() const;
   std::string getChecksum();
   void invalidateChecksum();
 
  private:
-  static std::string computeChecksum(const std::string& file_location);
+  static std::string computeChecksum(const std::filesystem::path& file_location);
 
-  std::optional<std::string> file_location_;
-  std::optional<std::string> file_name_;
+  std::optional<std::filesystem::path> file_location_;
+  std::optional<std::filesystem::path> file_name_;
   std::optional<std::string> checksum_;
 };
 
@@ -49,8 +46,4 @@ inline void ChecksumCalculator::invalidateChecksum() {
   checksum_.reset();
 }
 
-}  // namespace utils
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
+}  // namespace org::apache::nifi::minifi::utils
diff --git a/libminifi/include/utils/Environment.h b/libminifi/include/utils/Environment.h
index ff87a9f1c..262df02ab 100644
--- a/libminifi/include/utils/Environment.h
+++ b/libminifi/include/utils/Environment.h
@@ -1,3 +1,4 @@
+
 /**
  * Licensed to the Apache Software Foundation (ASF) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
@@ -19,13 +20,10 @@
 
 #include <utility>
 #include <functional>
+#include <optional>
 #include <string>
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace utils {
+namespace org::apache::nifi::minifi::utils {
 
 /**
  * A helper class for interacting with the environment in a thread-safe manner.
@@ -45,7 +43,7 @@ class Environment {
    * @return a pair consisting of a bool indicating whether the environment variable is set
    * and an std::string containing the value of the environemnt variable
    */
-  static std::pair<bool, std::string> getEnvironmentVariable(const char* name);
+  static std::optional<std::string> getEnvironmentVariable(const char* name);
 
   /**
    * Sets an environment variable using the native OS API
@@ -63,19 +61,6 @@ class Environment {
    */
   static bool unsetEnvironmentVariable(const char* name);
 
-  /**
-   * Determines the current working directory
-   * @return current working directory on success, empty string on failure
-   */
-  static std::string getCurrentWorkingDirectory();
-
-  /**
-   * Changes the current working directory
-   * @param directory the directory to change to
-   * @return true on success
-   */
-  static bool setCurrentWorkingDirectory(const char* directory);
-
   /**
    * Sets whether the current process is running as a service
    * @param runningAsService true if the current process is running as a service
@@ -89,10 +74,6 @@ class Environment {
   static bool isRunningAsService();
 };
 
-}  // namespace utils
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
+}  // namespace org::apache::nifi::minifi::utils
 
 #endif  // LIBMINIFI_INCLUDE_UTILS_ENVIRONMENT_H_
diff --git a/libminifi/include/utils/FileReaderCallback.h b/libminifi/include/utils/FileReaderCallback.h
index 3d8233716..e78607be5 100644
--- a/libminifi/include/utils/FileReaderCallback.h
+++ b/libminifi/include/utils/FileReaderCallback.h
@@ -18,6 +18,7 @@
 
 #include <memory>
 #include <fstream>
+#include <filesystem>
 #include <stdexcept>
 #include <string>
 
@@ -31,11 +32,11 @@ namespace org::apache::nifi::minifi::utils {
  */
 class FileReaderCallback {
  public:
-  explicit FileReaderCallback(std::string file_name);
+  explicit FileReaderCallback(std::filesystem::path file_path);
   int64_t operator()(const std::shared_ptr<io::OutputStream>& output_stream) const;
 
  private:
-  std::string file_name_;
+  std::filesystem::path file_path_;
   std::shared_ptr<core::logging::Logger> logger_;
 };
 
diff --git a/libminifi/include/utils/TestUtils.h b/libminifi/include/utils/TestUtils.h
index 99b964037..367bdf2da 100644
--- a/libminifi/include/utils/TestUtils.h
+++ b/libminifi/include/utils/TestUtils.h
@@ -33,14 +33,10 @@
 #include "date/tz.h"
 #endif
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace utils {
+namespace org::apache::nifi::minifi::utils {
 
-std::string putFileToDir(const std::string& dir_path, const std::string& file_name, const std::string& content) {
-  std::string file_path(file::FileUtils::concat_path(dir_path, file_name));
+std::filesystem::path putFileToDir(const std::filesystem::path& dir_path, const std::filesystem::path& file_name, const std::string& content) {
+  auto file_path = dir_path/file_name;
   std::ofstream out_file(file_path, std::ios::binary | std::ios::out);
   if (out_file.is_open()) {
     out_file << content;
@@ -48,7 +44,7 @@ std::string putFileToDir(const std::string& dir_path, const std::string& file_na
   return file_path;
 }
 
-std::string getFileContent(const std::string& file_name) {
+std::string getFileContent(const std::filesystem::path& file_name) {
   std::ifstream file_handle(file_name, std::ios::binary | std::ios::in);
   assert(file_handle.is_open());
   std::string file_content{ (std::istreambuf_iterator<char>(file_handle)), (std::istreambuf_iterator<char>()) };
@@ -113,8 +109,4 @@ class ManualClock : public timeutils::SteadyClock {
 void dateSetInstall(const std::string& install);
 #endif
 
-}  // namespace utils
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
+}  // namespace org::apache::nifi::minifi::utils
diff --git a/libminifi/include/utils/crypto/EncryptionManager.h b/libminifi/include/utils/crypto/EncryptionManager.h
index f538471ff..6db38e03b 100644
--- a/libminifi/include/utils/crypto/EncryptionManager.h
+++ b/libminifi/include/utils/crypto/EncryptionManager.h
@@ -21,6 +21,7 @@
 #include <optional>
 #include <string>
 #include <utility>
+#include <filesystem>
 #include "utils/crypto/EncryptionUtils.h"
 #include "utils/crypto/ciphers/XSalsa20.h"
 #include "utils/crypto/ciphers/Aes256Ecb.h"
@@ -36,7 +37,7 @@ namespace crypto {
 class EncryptionManager {
   static std::shared_ptr<core::logging::Logger> logger_;
  public:
-  explicit EncryptionManager(std::string key_dir) : key_dir_(std::move(key_dir)) {}
+  explicit EncryptionManager(std::filesystem::path key_dir) : key_dir_(std::move(key_dir)) {}
 
   [[nodiscard]] std::optional<XSalsa20Cipher> createXSalsa20Cipher(const std::string& key_name) const;
   [[nodiscard]] std::optional<Aes256EcbCipher> createAes256EcbCipher(const std::string& key_name) const;
@@ -44,7 +45,7 @@ class EncryptionManager {
   [[nodiscard]] virtual std::optional<Bytes> readKey(const std::string& key_name) const;
   [[nodiscard]] virtual bool writeKey(const std::string& key_name, const Bytes& key) const;
 
-  std::string key_dir_;
+  std::filesystem::path key_dir_;
 };
 
 }  // namespace crypto
diff --git a/libminifi/include/utils/crypto/EncryptionProvider.h b/libminifi/include/utils/crypto/EncryptionProvider.h
index 6c20ff1dc..e095667f0 100644
--- a/libminifi/include/utils/crypto/EncryptionProvider.h
+++ b/libminifi/include/utils/crypto/EncryptionProvider.h
@@ -21,6 +21,7 @@
 #include <optional>
 #include <string>
 #include <utility>
+#include <filesystem>
 
 #include "utils/crypto/EncryptionUtils.h"
 #include "utils/crypto/ciphers/XSalsa20.h"
@@ -38,7 +39,7 @@ class EncryptionProvider {
   explicit EncryptionProvider(Bytes key) : cipher_impl_(std::move(key)) {}
   explicit EncryptionProvider(XSalsa20Cipher cipher_impl) : cipher_impl_(std::move(cipher_impl)) {}
 
-  static std::optional<EncryptionProvider> create(const std::string& home_path);
+  static std::optional<EncryptionProvider> create(const std::filesystem::path& home_path);
 
   [[nodiscard]] std::string encrypt(const std::string& data) const {
     return cipher_impl_.encrypt(data);
diff --git a/libminifi/include/utils/file/FileManager.h b/libminifi/include/utils/file/FileManager.h
index 964c84e71..70ddfe271 100644
--- a/libminifi/include/utils/file/FileManager.h
+++ b/libminifi/include/utils/file/FileManager.h
@@ -14,21 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#ifndef LIBMINIFI_INCLUDE_UTILS_FILE_FILEMANAGER_H_
-#define LIBMINIFI_INCLUDE_UTILS_FILE_FILEMANAGER_H_
+#pragma once
 
 #include <string>
 #include <vector>
-
-#ifdef USE_BOOST
-#include <boost/filesystem.hpp>
-
-#else
 #include <cstdlib>
-
-#endif
-#include <fcntl.h>
-
 #include <cstdio>
 
 #include "io/validation.h"
@@ -36,21 +26,7 @@
 #include "utils/StringUtils.h"
 #include "utils/file/FileUtils.h"
 
-#ifndef FILE_SEPARATOR
-  #ifdef WIN32
-  #define FILE_SEPARATOR "\\"
-  #else
-  #define FILE_SEPARATOR "/"
-  #endif
-#endif
-
-
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace utils {
-namespace file {
+namespace org::apache::nifi::minifi::utils::file {
 
 /**
  * Simple implementation of simple file manager utilities.
@@ -62,53 +38,31 @@ class FileManager {
   FileManager() = default;
 
   ~FileManager() {
-    for (auto file : unique_files_) {
-      std::remove(file.c_str());
+    for (const auto& file : unique_files_) {
+      std::filesystem::remove(file);
     }
   }
-  std::string unique_file(const std::string &location, bool keep = false) {
-    const std::string& dir = !IsNullOrEmpty(location) ? location : utils::file::FileUtils::get_temp_directory();
+  std::filesystem::path unique_file(const std::filesystem::path& location, bool keep = false) {
+    auto dir = !location.empty() ? location : std::filesystem::temp_directory_path();
 
-    std::string file_name = utils::file::FileUtils::concat_path(dir, non_repeating_string_generator_.generate());
-    while (!verify_not_exist(file_name)) {
-      file_name = utils::file::FileUtils::concat_path(dir, non_repeating_string_generator_.generate());
+    auto file_name = dir / non_repeating_string_generator_.generate();
+    while (std::filesystem::exists(file_name)) {
+      file_name = dir / non_repeating_string_generator_.generate();
     }
     if (!keep)
       unique_files_.push_back(file_name);
     return file_name;
   }
 
-  std::string unique_file(bool keep = false) {
-#ifdef USE_BOOST
-    (void)keep;  // against unused variable warnings
-    return boost::filesystem::unique_path().native();
-#else  // USE_BOOST
+  std::filesystem::path unique_file(bool keep = false) {
     return unique_file(std::string{}, keep);
-#endif  // USE_BOOST
   }
 
 
  protected:
-  inline bool verify_not_exist(const std::string& name) {
-#ifdef WIN32
-    struct _stat buffer;
-    return _stat(name.c_str(), &buffer) != 0;
-#else
-    struct stat buffer;
-    return stat(name.c_str(), &buffer) != 0;
-#endif
-  }
-
   utils::NonRepeatingStringGenerator non_repeating_string_generator_;
 
-  std::vector<std::string> unique_files_;
+  std::vector<std::filesystem::path> unique_files_;
 };
 
-}  // namespace file
-}  // namespace utils
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
-
-#endif  // LIBMINIFI_INCLUDE_UTILS_FILE_FILEMANAGER_H_
+}  // namespace org::apache::nifi::minifi::utils::file
diff --git a/libminifi/include/utils/file/FilePattern.h b/libminifi/include/utils/file/FilePattern.h
index 975c4d486..9ecad2e27 100644
--- a/libminifi/include/utils/file/FilePattern.h
+++ b/libminifi/include/utils/file/FilePattern.h
@@ -27,12 +27,7 @@
 
 struct FilePatternTestAccessor;
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace utils {
-namespace file {
+namespace org::apache::nifi::minifi::utils::file {
 
 class FilePatternError : public std::invalid_argument {
  public:
@@ -59,15 +54,15 @@ class FilePattern {
       NOT_MATCHING  // dir/file does not match pattern, do what you may
     };
 
-    bool isExcluding() const {
+    [[nodiscard]] bool isExcluding() const {
       return excluding_;
     }
 
-    MatchResult match(const std::string& directory) const;
+    [[nodiscard]] MatchResult matchDir(const std::filesystem::path& directory) const;
 
-    MatchResult match(const std::string& directory, const std::string& filename) const;
+    [[nodiscard]] MatchResult matchFile(const std::filesystem::path& directory, const std::filesystem::path& filename) const;
 
-    MatchResult match(const std::filesystem::path& path) const;
+    [[nodiscard]] MatchResult match(const std::filesystem::path& path) const;
     /**
      * @return The lowermost parent directory without wildcards.
      */
@@ -83,7 +78,7 @@ class FilePattern {
     };
 
     using DirIt = std::filesystem::path::const_iterator;
-    static DirMatchResult matchDirectory(DirIt pattern_it, DirIt pattern_end, DirIt value_it, DirIt value_end);
+    static DirMatchResult matchDirectory(DirIt pattern_it, const DirIt& pattern_end, DirIt value_it, const DirIt& value_end);
 
     std::filesystem::path directory_pattern_;
     std::string file_pattern_;
@@ -101,7 +96,7 @@ class FilePattern {
   }
 
  public:
-  explicit FilePattern(const std::string& pattern, ErrorHandler error_handler = defaultErrorHandler);
+  explicit FilePattern(const std::string& pattern, const ErrorHandler& error_handler = defaultErrorHandler);
 
  private:
   std::vector<FilePatternSegment> segments_;
@@ -109,9 +104,4 @@ class FilePattern {
 
 std::set<std::filesystem::path> match(const FilePattern& pattern);
 
-}  // namespace file
-}  // namespace utils
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
+}  // namespace org::apache::nifi::minifi::utils::file
diff --git a/libminifi/include/utils/file/FileSystem.h b/libminifi/include/utils/file/FileSystem.h
index cc873accb..828e6262a 100644
--- a/libminifi/include/utils/file/FileSystem.h
+++ b/libminifi/include/utils/file/FileSystem.h
@@ -39,9 +39,9 @@ class FileSystem {
   FileSystem& operator=(const FileSystem&) = delete;
   FileSystem& operator=(FileSystem&&) = delete;
 
-  std::optional<std::string> read(const std::string& file_name);
+  std::optional<std::string> read(const std::filesystem::path& file_name);
 
-  bool write(const std::string& file_name, const std::string& file_content);
+  bool write(const std::filesystem::path& file_name, const std::string& file_content);
 
  private:
   bool should_encrypt_on_write_;
diff --git a/libminifi/include/utils/file/FileUtils.h b/libminifi/include/utils/file/FileUtils.h
index d1b582840..e4fb301d5 100644
--- a/libminifi/include/utils/file/FileUtils.h
+++ b/libminifi/include/utils/file/FileUtils.h
@@ -14,7 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 #pragma once
 
 #include <filesystem>
@@ -84,59 +83,11 @@ namespace org::apache::nifi::minifi::utils::file {
 
 namespace FileUtils = ::org::apache::nifi::minifi::utils::file;
 
-namespace detail {
-static inline int platform_create_dir(const std::string& path) {
-#ifdef WIN32
-  return _mkdir(path.c_str());
-#else
-  return mkdir(path.c_str(), 0777);
-#endif
-}
-}  // namespace detail
-
-/*
- * Get the platform-specific path separator.
- * @param force_posix returns the posix path separator ('/'), even when not on posix. Useful when dealing with remote posix paths.
- * @return the path separator character
- */
-#ifdef WIN32
-inline char get_separator(bool force_posix = false) {
-  if (!force_posix) {
-    return '\\';
-  }
-  return '/';
-}
-#else
-inline char get_separator(bool /*force_posix*/ = false) {
-  return '/';
-}
-#endif
 time_t to_time_t(std::filesystem::file_time_type time);
 
 std::chrono::system_clock::time_point to_sys(std::filesystem::file_time_type time);
 
-inline std::string normalize_path_separators(std::string path, bool force_posix = false) {
-  const auto normalize_separators = [force_posix](const char c) {
-    if (c == '\\' || c == '/') { return get_separator(force_posix); }
-    return c;
-  };
-  std::transform(std::begin(path), std::end(path), std::begin(path), normalize_separators);
-  return path;
-}
-
-inline std::string get_temp_directory() {
-#ifdef WIN32
-  char tempBuffer[MAX_PATH];
-  const auto ret = GetTempPath(MAX_PATH, tempBuffer);
-  if (ret > MAX_PATH || ret == 0)
-    throw std::runtime_error("Couldn't locate temporary directory path");
-  return tempBuffer;
-#else
-  return "/tmp";
-#endif
-}
-
-inline int64_t delete_dir(const std::string &path, bool delete_files_recursively = true) {
+inline int64_t delete_dir(const std::filesystem::path& path, bool delete_files_recursively = true) {
   // Empty path is interpreted as the root of the current partition on Windows, which should not be allowed
   if (path.empty()) {
     return -1;
@@ -149,7 +100,7 @@ inline int64_t delete_dir(const std::string &path, bool delete_files_recursively
         std::filesystem::remove(path);
       }
     }
-  } catch (std::filesystem::filesystem_error const &) {
+  } catch (const std::filesystem::filesystem_error&) {
     return -1;
     // display error message
   }
@@ -157,7 +108,7 @@ inline int64_t delete_dir(const std::string &path, bool delete_files_recursively
 }
 
 inline std::chrono::time_point<std::chrono::file_clock,
-    std::chrono::seconds> last_write_time_point(const std::string &path) {
+    std::chrono::seconds> last_write_time_point(const std::filesystem::path& path) {
   std::error_code ec;
   auto result = std::filesystem::last_write_time(path, ec);
   if (ec.value() == 0) {
@@ -166,7 +117,7 @@ inline std::chrono::time_point<std::chrono::file_clock,
   return std::chrono::time_point<std::chrono::file_clock, std::chrono::seconds>{};
 }
 
-inline std::optional<std::filesystem::file_time_type> last_write_time(const std::string &path) {
+inline std::optional<std::filesystem::file_time_type> last_write_time(const std::filesystem::path& path) {
   std::error_code ec;
   auto result = std::filesystem::last_write_time(path, ec);
   if (ec.value() == 0) {
@@ -177,51 +128,44 @@ inline std::optional<std::filesystem::file_time_type> last_write_time(const std:
 
 inline std::optional<std::string> format_time(const std::filesystem::file_time_type& time, const std::string& format) {
   auto last_write_time_t = to_time_t(time);
-  std::array<char, 128U> result;
+  std::array<char, 128U> result{};
   if (std::strftime(result.data(), result.size(), format.c_str(), gmtime(&last_write_time_t)) != 0) {
     return std::string(result.data());
   }
   return std::nullopt;
 }
 
-inline std::optional<std::string> get_last_modified_time_formatted_string(const std::string& path, const std::string& format_string) {
-  return last_write_time(path) | utils::flatMap([format_string](auto time) { return format_time(time, format_string); });
+inline std::optional<std::string> get_last_modified_time_formatted_string(const std::filesystem::path& path, const std::string& format_string) {
+  return utils::file::last_write_time(path) | utils::flatMap([format_string](auto time) { return format_time(time, format_string); });
 }
 
-inline bool set_last_write_time(const std::string &path, std::filesystem::file_time_type new_time) {
+inline bool set_last_write_time(const std::filesystem::path& path, std::filesystem::file_time_type new_time) {
   std::error_code ec;
   std::filesystem::last_write_time(path, new_time, ec);
   return ec.value() == 0;
 }
 
-inline uint64_t file_size(const std::string &path) {
-#ifdef WIN32
-  struct _stat64 result;
-  if (_stat64(path.c_str(), &result) == 0) {
-    return result.st_size;
-  }
-#else
-  struct stat result;
-  if (stat(path.c_str(), &result) == 0) {
-    return result.st_size;
-  }
-#endif
-  return 0;
+inline uint64_t file_size(const std::filesystem::path& path) {
+  std::error_code ec;
+  auto file_size = std::filesystem::file_size(path, ec);
+  if (ec.value() != 0)
+    return 0;
+  return file_size;
 }
 
-inline bool get_permissions(const std::string &path, uint32_t &permissions) {
+inline bool get_permissions(const std::filesystem::path& path, uint32_t& permissions) {
   std::error_code ec;
   permissions = static_cast<uint32_t>(std::filesystem::status(path, ec).permissions());
   return ec.value() == 0;
 }
 
-inline int set_permissions(const std::string &path, const uint32_t permissions) {
+inline int set_permissions(const std::filesystem::path& path, const uint32_t permissions) {
   std::error_code ec;
   std::filesystem::permissions(path, static_cast<std::filesystem::perms>(permissions), ec);
   return ec.value();
 }
 
-inline std::optional<std::string> get_permission_string(const std::string &path) {
+inline std::optional<std::string> get_permission_string(const std::filesystem::path& path) {
   std::error_code ec;
   auto permissions = std::filesystem::status(path, ec).permissions();
   if (ec.value() != 0) {
@@ -243,7 +187,7 @@ inline std::optional<std::string> get_permission_string(const std::string &path)
 
 #ifndef WIN32
 inline bool get_uid_gid(const std::string &path, uint64_t &uid, uint64_t &gid) {
-  struct stat result;
+  struct stat result = {};
   if (stat(path.c_str(), &result) == 0) {
     uid = result.st_uid;
     gid = result.st_gid;
@@ -262,7 +206,7 @@ inline bool is_directory(const std::filesystem::path &path) {
   return false;
 }
 
-inline bool exists(const std::string &path) {
+inline bool exists(const std::filesystem::path &path) {
   std::error_code ec;
   bool result = std::filesystem::exists(path, ec);
   if (ec.value() == 0) {
@@ -271,7 +215,7 @@ inline bool exists(const std::string &path) {
   return false;
 }
 
-inline int create_dir(const std::string &path, bool recursive = true) {
+inline int create_dir(const std::filesystem::path& path, bool recursive = true) {
   std::filesystem::path dir(path);
   std::error_code ec;
   if (!recursive) {
@@ -285,65 +229,48 @@ inline int create_dir(const std::string &path, bool recursive = true) {
   return ec.value();
 }
 
-inline int copy_file(const std::string &path_from, const std::string& dest_path) {
-  std::ifstream src(path_from, std::ios::binary);
-  if (!src.is_open())
+inline int copy_file(const std::filesystem::path& path_from, const std::filesystem::path& dest_path) {
+  std::error_code ec;
+  auto copy_success = std::filesystem::copy_file(path_from, dest_path, std::filesystem::copy_options::overwrite_existing, ec);
+  if (ec.value() != 0 || !copy_success)
     return -1;
-  std::ofstream dest(dest_path, std::ios::binary);
-  dest << src.rdbuf();
   return 0;
 }
 
 inline void addFilesMatchingExtension(const std::shared_ptr<core::logging::Logger> &logger,
-                                      const std::string &originalPath,
-                                      const std::string &extension,
-                                      std::vector<std::string> &accruedFiles) {
+                                      const std::filesystem::path& originalPath,
+                                      const std::filesystem::path& extension,
+                                      std::vector<std::filesystem::path>& accruedFiles) {
   if (!utils::file::exists(originalPath)) {
-    logger->log_warn("Failed to open directory: %s", originalPath.c_str());
+    logger->log_warn("Failed to open directory: %s", originalPath.string());
     return;
   }
 
   if (utils::file::is_directory(originalPath)) {
     // only perform a listing while we are not empty
-    logger->log_debug("Looking for files with %s extension in %s", extension, originalPath);
+    logger->log_debug("Looking for files with %s extension in %s", extension.string(), originalPath.string());
 
-    for (const auto &entry: std::filesystem::directory_iterator(originalPath,
+    for (const auto& entry: std::filesystem::directory_iterator(originalPath,
                                                                 std::filesystem::directory_options::skip_permission_denied)) {
-      std::string d_name = entry.path().filename().string();
-      std::string path = entry.path().string();
-
-      if (utils::file::is_directory(path)) {          // if this is a directory
-        addFilesMatchingExtension(logger, path, extension, accruedFiles);
+      if (utils::file::is_directory(entry.path())) {          // if this is a directory
+        addFilesMatchingExtension(logger, entry.path(), extension, accruedFiles);
       } else {
-        if (utils::StringUtils::endsWith(path, extension)) {
-          logger->log_info("Adding %s to paths", path);
-          accruedFiles.push_back(path);
+        if (entry.path().extension() == extension) {
+          logger->log_info("Adding %s to paths", entry.path().string());
+          accruedFiles.push_back(entry.path());
         }
       }
     }
   } else if (std::filesystem::is_regular_file(originalPath)) {
-    if (utils::StringUtils::endsWith(originalPath, extension)) {
-      logger->log_info("Adding %s to paths", originalPath);
+    if (originalPath.extension() == extension) {
+      logger->log_info("Adding %s to paths", originalPath.string());
       accruedFiles.push_back(originalPath);
     }
   } else {
-    logger->log_error("Could not access %s", originalPath);
+    logger->log_error("Could not access %s", originalPath.string());
   }
 }
 
-inline std::string concat_path(const std::string& root, const std::string& child, bool force_posix = false) {
-  if (root.empty()) {
-    return child;
-  }
-  std::stringstream new_path;
-  if (root.back() == get_separator(force_posix)) {
-    new_path << root << child;
-  } else {
-    new_path << root << get_separator(force_posix) << child;
-  }
-  return new_path.str();
-}
-
 /**
  * Provides a platform-independent function to list a directory
  * @param dir The directory to start the enumeration from.
@@ -353,19 +280,19 @@ inline std::string concat_path(const std::string& root, const std::string& child
  * @param dir_callback Called for every child directory, its return value decides if we should descend and recursively
  * process that directory or not.
  */
-inline void list_dir(const std::string& dir,
-                     std::function<bool(const std::string&, const std::string&)> callback,
+inline void list_dir(const std::filesystem::path& dir,
+                     const std::function<bool(const std::filesystem::path&, const std::filesystem::path&)>& callback,
                      const std::shared_ptr<core::logging::Logger> &logger,
-                     std::function<bool(const std::string&)> dir_callback) {
-  logger->log_debug("Performing file listing against %s", dir);
+                     const std::function<bool(const std::filesystem::path&)>& dir_callback) {
+  logger->log_debug("Performing file listing against %s", dir.string());
   if (!utils::file::exists(dir)) {
-    logger->log_warn("Failed to open directory: %s", dir.c_str());
+    logger->log_warn("Failed to open directory: %s", dir.string());
     return;
   }
 
   for (const auto &entry: std::filesystem::directory_iterator(dir, std::filesystem::directory_options::skip_permission_denied)) {
-    std::string d_name = entry.path().filename().string();
-    std::string path = entry.path().string();
+    auto d_name = entry.path().filename();
+    auto path = entry.path();
 
     if (utils::file::is_directory(path)) {  // if this is a directory
       if (dir_callback(path)) {
@@ -379,20 +306,20 @@ inline void list_dir(const std::string& dir,
   }
 }
 
-inline void list_dir(const std::string& dir,
-                     std::function<bool(const std::string&, const std::string&)> callback,
+inline void list_dir(const std::filesystem::path& dir,
+                     const std::function<bool(const std::filesystem::path&, const std::filesystem::path&)>& callback,
                      const std::shared_ptr<core::logging::Logger> &logger,
                      bool recursive = true) {
-  list_dir(dir, callback, logger, [&] (const std::string&) {
+  list_dir(dir, callback, logger, [&] (const std::filesystem::path&) {
     return recursive;
   });
 }
 
-inline std::vector<std::pair<std::string, std::string>> list_dir_all(const std::string& dir, const std::shared_ptr<core::logging::Logger> &logger,
+inline std::vector<std::pair<std::filesystem::path, std::filesystem::path>> list_dir_all(const std::filesystem::path& dir, const std::shared_ptr<core::logging::Logger> &logger,
     bool recursive = true)  {
-  std::vector<std::pair<std::string, std::string>> fileList;
-  auto lambda = [&fileList] (const std::string &path, const std::string &filename) {
-    fileList.push_back(make_pair(path, filename));
+  std::vector<std::pair<std::filesystem::path, std::filesystem::path>> fileList;
+  auto lambda = [&fileList] (const std::filesystem::path& parent_path, const std::filesystem::path& filename) {
+    fileList.emplace_back(parent_path, filename);
     return true;
   };
 
@@ -401,10 +328,9 @@ inline std::vector<std::pair<std::string, std::string>> list_dir_all(const std::
   return fileList;
 }
 
-inline std::string create_temp_directory(char* format) {
+inline std::filesystem::path create_temp_directory(char* format) {
 #ifdef WIN32
-  const std::string tempDirectory = concat_path(get_temp_directory(),
-      minifi::utils::IdGenerator::getIdGenerator()->generate().to_string());
+  const auto tempDirectory = std::filesystem::temp_directory_path() / minifi::utils::IdGenerator::getIdGenerator()->generate().to_string().view();
   create_dir(tempDirectory);
   return tempDirectory;
 #else
@@ -413,101 +339,19 @@ inline std::string create_temp_directory(char* format) {
 #endif
 }
 
-inline std::tuple<std::string /*parent_path*/, std::string /*child_path*/> split_path(const std::string& path, bool force_posix = false) {
-  if (path.empty()) {
-    /* Empty path has no parent and no child*/
-    return std::make_tuple("", "");
-  }
-  bool absolute = false;
-  size_t root_pos = 0U;
-#ifdef WIN32
-  if (!force_posix) {
-      if (path[0] == '\\') {
-        absolute = true;
-        if (path.size() < 2U) {
-          return std::make_tuple("", "");
-        }
-        if (path[1] == '\\') {
-          if (path.size() >= 4U &&
-             (path[2] == '?' || path[2] == '.') &&
-              path[3] == '\\') {
-            /* Probably an UNC path */
-            root_pos = 4U;
-          } else {
-            /* Probably a \\server\-type path */
-            root_pos = 2U;
-          }
-          root_pos = path.find_first_of("\\", root_pos);
-          if (root_pos == std::string::npos) {
-            return std::make_tuple("", "");
-          }
-        }
-      } else if (path.size() >= 3U &&
-                 toupper(path[0]) >= 'A' &&
-                 toupper(path[0]) <= 'Z' &&
-                 path[1] == ':' &&
-                 path[2] == '\\') {
-        absolute = true;
-        root_pos = 2U;
-      }
-    } else {
-#else
-  if (true) {
-#endif
-    if (path[0] == '/') {
-      absolute = true;
-      root_pos = 0U;
-    }
-  }
-  /* Maybe we are just a single relative child */
-  if (!absolute && path.find(get_separator(force_posix)) == std::string::npos) {
-    return std::make_tuple("", path);
-  }
-  /* Ignore trailing separators */
-  size_t last_pos = path.size() - 1;
-  while (last_pos > root_pos && path[last_pos] == get_separator(force_posix)) {
-    last_pos--;
-  }
-  if (absolute && last_pos == root_pos) {
-    /* This means we are only a root */
-    return std::make_tuple("", "");
-  }
-  /* Find parent-child separator */
-  size_t last_separator = path.find_last_of(get_separator(force_posix), last_pos);
-  if (last_separator == std::string::npos || last_separator < root_pos) {
-    return std::make_tuple("", "");
-  }
-  std::string parent = path.substr(0, last_separator + 1);
-  std::string child = path.substr(last_separator + 1);
-
-  return std::make_tuple(std::move(parent), std::move(child));
-}
-
-inline std::string get_parent_path(const std::string& path, bool force_posix = false) {
-  std::string parent_path;
-  std::tie(parent_path, std::ignore) = split_path(path, force_posix);
-  return parent_path;
-}
-
-inline std::string get_child_path(const std::string& path, bool force_posix = false) {
-  std::string child_path;
-  std::tie(std::ignore, child_path) = split_path(path, force_posix);
-  return child_path;
-}
-
-inline bool is_hidden(const std::string& path) {
+inline bool is_hidden(const std::filesystem::path& path) {
 #ifdef WIN32
-  DWORD attributes = GetFileAttributesA(path.c_str());
-    return ((attributes != INVALID_FILE_ATTRIBUTES)  && ((attributes & FILE_ATTRIBUTE_HIDDEN) != 0));
+  DWORD attributes = GetFileAttributesA(path.string().c_str());
+  return ((attributes != INVALID_FILE_ATTRIBUTES) && ((attributes & FILE_ATTRIBUTE_HIDDEN) != 0));
 #else
-  return std::get<1>(split_path(path)).rfind(".", 0) == 0;
+  return path.filename().string().starts_with('.');
 #endif
 }
 
 /*
  * Returns the absolute path of the current executable
  */
-inline std::string get_executable_path() {
+inline std::filesystem::path get_executable_path() {
 #if defined(__linux__)
   std::vector<char> buf(1024U);
   while (true) {
@@ -556,22 +400,22 @@ inline std::string get_executable_path() {
 #endif
 }
 
-inline std::string resolve(const std::string& base, const std::string& path) {
-  if (utils::file::isAbsolutePath(path.c_str())) {
+inline std::filesystem::path resolve(const std::filesystem::path& base, const std::filesystem::path& path) {
+  if (path.is_absolute()) {
     return path;
   }
-  return concat_path(base, path);
+  return base / path;
 }
 
 /*
  * Returns the absolute path to the directory containing the current executable
  */
-inline std::string get_executable_dir() {
+inline std::filesystem::path get_executable_dir() {
   auto executable_path = get_executable_path();
   if (executable_path.empty()) {
     return "";
   }
-  return get_parent_path(executable_path);
+  return executable_path.parent_path();
 }
 
 inline int close(int file_descriptor) {
@@ -582,50 +426,29 @@ inline int close(int file_descriptor) {
 #endif
 }
 
-inline int access(const char *path_name, int mode) {
-#ifdef WIN32
-  return _access(path_name, mode);
-#else
-  return ::access(path_name, mode);
-#endif
-}
-#ifdef WIN32
-inline std::error_code hide_file(const char* const file_name) {
-    const bool success = SetFileAttributesA(file_name, FILE_ATTRIBUTE_HIDDEN);
-    if (!success) {
-      // note: All possible documented error codes from GetLastError are in [0;15999] at the time of writing.
-      // The below casting is safe in [0;std::numeric_limits<int>::max()], int max is guaranteed to be at least 32767
-      return { static_cast<int>(GetLastError()), std::system_category() };
-    }
-    return {};
-  }
-#endif /* WIN32 */
+uint64_t computeChecksum(const std::filesystem::path& file_name, uint64_t up_to_position);
 
-uint64_t computeChecksum(const std::string &file_name, uint64_t up_to_position);
-
-inline std::string get_content(const std::string &file_name) {
+inline std::string get_content(const std::filesystem::path& file_name) {
   std::ifstream file(file_name, std::ifstream::binary);
   std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
   return content;
 }
 
-void put_content(const std::filesystem::path& filename, std::string_view new_contents);
-
 bool contains(const std::filesystem::path& file_path, std::string_view text_to_search);
 
 
-inline std::optional<std::string> get_file_owner(const std::string& file_path) {
+inline std::optional<std::string> get_file_owner(const std::filesystem::path& file_path) {
 #ifndef WIN32
-  struct stat info;
+  struct stat info = {};
   if (stat(file_path.c_str(), &info) != 0) {
     return std::nullopt;
   }
 
-  struct passwd pw;
-  pw.pw_name = 0;
+  struct passwd pw = {};
+  pw.pw_name = nullptr;
   struct passwd *result = nullptr;
   char localbuf[1024] = {};
-  if (getpwuid_r(info.st_uid, &pw, localbuf, sizeof(localbuf), &result) != 0 || pw.pw_name == 0) {
+  if (getpwuid_r(info.st_uid, &pw, localbuf, sizeof(localbuf), &result) != 0 || pw.pw_name == nullptr) {
     return std::nullopt;
   }
 
@@ -644,7 +467,7 @@ inline std::optional<std::string> get_file_owner(const std::string& file_path) {
 
   // Get the handle of the file object.
   file_handle = CreateFile(
-    TEXT(file_path.c_str()),
+    TEXT(file_path.string().c_str()),
     GENERIC_READ,
     FILE_SHARE_READ,
     NULL,
@@ -725,17 +548,17 @@ inline std::optional<std::string> get_file_owner(const std::string& file_path) {
 }
 
 #ifndef WIN32
-inline std::optional<std::string> get_file_group(const std::string& file_path) {
-  struct stat info;
+inline std::optional<std::string> get_file_group(const std::filesystem::path& file_path) {
+  struct stat info = {};
   if (stat(file_path.c_str(), &info) != 0) {
     return std::nullopt;
   }
 
-  struct group gr;
-  gr.gr_name = 0;
+  struct group gr = {};
+  gr.gr_name = nullptr;
   struct group *result = nullptr;
   char localbuf[1024] = {};
-  if ((getgrgid_r(info.st_uid, &gr, localbuf, sizeof(localbuf), &result) != 0) || gr.gr_name == 0) {
+  if ((getgrgid_r(info.st_uid, &gr, localbuf, sizeof(localbuf), &result) != 0) || gr.gr_name == nullptr) {
     return std::nullopt;
   }
 
@@ -743,12 +566,12 @@ inline std::optional<std::string> get_file_group(const std::string& file_path) {
 }
 #endif
 
-inline std::optional<std::string> get_relative_path(const std::string& path, const std::string& base_path) {
-  if (!utils::StringUtils::startsWith(path, base_path)) {
+inline std::optional<std::filesystem::path> get_relative_path(const std::filesystem::path& path, const std::filesystem::path& base_path) {
+  if (!utils::StringUtils::startsWith(path.string(), base_path.string())) {
     return std::nullopt;
   }
 
-  return std::filesystem::relative(path, base_path).string();
+  return std::filesystem::relative(path, base_path);
 }
 
 }  // namespace org::apache::nifi::minifi::utils::file
diff --git a/libminifi/include/utils/file/PathUtils.h b/libminifi/include/utils/file/PathUtils.h
index ebbf19830..1df2e9d89 100644
--- a/libminifi/include/utils/file/PathUtils.h
+++ b/libminifi/include/utils/file/PathUtils.h
@@ -19,6 +19,7 @@
 #include <climits>
 #include <cctype>
 #include <cinttypes>
+#include <filesystem>
 #include <memory>
 #include <optional>
 #include <string>
@@ -30,47 +31,15 @@ namespace org::apache::nifi::minifi::utils::file {
 namespace PathUtils = ::org::apache::nifi::minifi::utils::file;
 using path = const char*;
 
-/**
- * Extracts the filename and path performing some validation of the path and output to ensure
- * we don't provide invalid results.
- * @param path input path
- * @param filePath output file path
- * @param fileName output file name
- * @return result of the operation.
- */
-bool getFileNameAndPath(const std::string &path, std::string &filePath, std::string &fileName);
-
-/**
- * Resolves the supplied path to an absolute pathname using the native OS functions
- * (realpath(3) on *nix, GetFullPathNameA on Windows)
- * @param path the name of the file
- * @return the canonicalized absolute pathname on success, empty string on failure
- */
-std::string getFullPath(const std::string& path);
-
 std::string globToRegex(std::string glob);
 
-inline bool isAbsolutePath(const char* const path) noexcept {
-#ifdef _WIN32
-  return path && std::isalpha(path[0]) && path[1] == ':' && (path[2] == '\\' || path[2] == '/');
-#else
-  return path && path[0] == '/';
-#endif
-}
-
-inline std::optional<std::string> canonicalize(const std::string &path) {
-  const char *resolved = nullptr;
-#ifndef WIN32
-  char full_path[PATH_MAX];
-  resolved = realpath(path.c_str(), full_path);
-#else
-  resolved = path.c_str();
-#endif
-
-  if (resolved == nullptr) {
+inline std::optional<std::filesystem::path> canonicalize(const std::filesystem::path& path) {
+  std::error_code canonical_error;
+  auto result = std::filesystem::canonical(path, canonical_error);
+  if (canonical_error)
     return std::nullopt;
-  }
-  return std::string(path);
+
+  return result;
 }
 
 
diff --git a/libminifi/include/utils/net/Ssl.h b/libminifi/include/utils/net/Ssl.h
index de8472889..7a32e30ac 100644
--- a/libminifi/include/utils/net/Ssl.h
+++ b/libminifi/include/utils/net/Ssl.h
@@ -27,9 +27,9 @@
 namespace org::apache::nifi::minifi::utils::net {
 
 struct SslData {
-  std::string ca_loc;
-  std::string cert_loc;
-  std::string key_loc;
+  std::filesystem::path ca_loc;
+  std::filesystem::path cert_loc;
+  std::filesystem::path key_loc;
   std::string key_pw;
 
   bool isValid() const {
diff --git a/libminifi/include/utils/tls/CertificateUtils.h b/libminifi/include/utils/tls/CertificateUtils.h
... 3971 lines suppressed ...