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:49 UTC

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

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 ...