You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jmeter.apache.org by vl...@apache.org on 2023/05/11 13:20:24 UTC

[jmeter] branch master updated: feat: use ServiceLoader to find implementations instead of searching classes in jars (#5885)

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

vladimirsitnikov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jmeter.git


The following commit(s) were added to refs/heads/master by this push:
     new eedabdd6c0 feat: use ServiceLoader to find implementations instead of searching classes in jars (#5885)
eedabdd6c0 is described below

commit eedabdd6c0c8030ee8063a937652c1ce59a4a042
Author: Vladimir Sitnikov <si...@gmail.com>
AuthorDate: Thu May 11 16:20:17 2023 +0300

    feat: use ServiceLoader to find implementations instead of searching classes in jars (#5885)
    
    ServiceLoader is Java standard approach for locating implementaitons,
    and it allows pluggability without relying on a filesystem layout.
    
    Fixes https://github.com/apache/jmeter/issues/5883
---
 build-logic/jvm/build.gradle.kts                   |   3 +-
 ...adle.kts => build-logic.autoservice.gradle.kts} |  37 +++---
 .../kotlin/build-logic.java-library.gradle.kts     |   1 +
 .../src/main/kotlin/build-logic.java.gradle.kts    |   1 +
 .../main/kotlin/build-logic.jvm-library.gradle.kts |   3 +-
 .../src/main/kotlin/build-logic.kotlin.gradle.kts  |   1 +
 build-logic/root-build/build.gradle.kts            |   1 +
 .../main/kotlin/build-logic.root-build.gradle.kts  |   1 +
 build.gradle.kts                                   |   3 +-
 gradle.properties                                  |   2 +
 settings.gradle.kts                                |   1 +
 src/bom-thirdparty/build.gradle.kts                |   2 +
 .../json/render/RenderAsJmesPathRenderer.java      |   3 +
 .../json/render/RenderAsJsonRenderer.java          |   4 +
 .../action/ExportTransactionAndSamplerNames.java   |   6 +
 .../visualizers/RenderAsBoundaryExtractor.java     |   3 +
 .../jmeter/visualizers/RenderAsCssJQuery.java      |   3 +
 .../jmeter/visualizers/RenderAsDocument.java       |   3 +
 .../apache/jmeter/visualizers/RenderAsHTML.java    |   3 +
 .../jmeter/visualizers/RenderAsHTMLFormatted.java  |   3 +
 .../visualizers/RenderAsHTMLWithEmbedded.java      |   3 +
 .../apache/jmeter/visualizers/RenderAsJSON.java    |   3 +
 .../apache/jmeter/visualizers/RenderAsRegexp.java  |   3 +
 .../apache/jmeter/visualizers/RenderAsText.java    |   3 +
 .../org/apache/jmeter/visualizers/RenderAsXML.java |   3 +
 .../apache/jmeter/visualizers/RenderAsXPath.java   |   3 +
 .../apache/jmeter/visualizers/RenderAsXPath2.java  |   3 +
 .../apache/jmeter/visualizers/RenderInBrowser.java |   3 +
 .../apache/jmeter/visualizers/RequestPanel.java    |  43 +++----
 .../org/apache/jmeter/visualizers/RequestView.java |   3 +
 .../apache/jmeter/visualizers/RequestViewRaw.java  |   3 +
 .../apache/jmeter/visualizers/ResultRenderer.java  |   2 +
 .../visualizers/ViewResultsFullVisualizer.java     |  43 +++----
 .../backend/AbstractBackendListenerClient.java     |   3 +
 .../visualizers/backend/BackendListenerClient.java |   2 +
 .../visualizers/backend/BackendListenerGui.java    |  34 ++---
 .../graphite/GraphiteBackendListenerClient.java    |   4 +
 .../influxdb/InfluxDBRawBackendListenerClient.java |   3 +
 .../influxdb/InfluxdbBackendListenerClient.java    |   4 +
 .../jmeter/engine/util/CompoundVariable.java       |  19 +--
 .../java/org/apache/jmeter/functions/Function.java |   6 +-
 .../org/apache/jmeter/gui/HtmlReportAction.java    |   7 ++
 .../org/apache/jmeter/gui/action/AboutCommand.java |   3 +
 .../org/apache/jmeter/gui/action/ActionRouter.java |   1 +
 .../org/apache/jmeter/gui/action/AddParent.java    |   3 +
 .../gui/action/AddThinkTimeBetweenEachStep.java    |   3 +
 .../org/apache/jmeter/gui/action/AddToTree.java    |   3 +
 .../jmeter/gui/action/ApplyNamingConvention.java   |   3 +
 .../apache/jmeter/gui/action/ChangeLanguage.java   |   3 +
 .../org/apache/jmeter/gui/action/ChangeParent.java |   3 +
 .../org/apache/jmeter/gui/action/CheckDirty.java   |   3 +
 .../java/org/apache/jmeter/gui/action/Clear.java   |   3 +
 .../java/org/apache/jmeter/gui/action/Close.java   |   3 +
 .../apache/jmeter/gui/action/CollapseExpand.java   |   3 +
 .../gui/action/CollapseExpandTreeBranch.java       |   3 +
 .../gui/action/CompileJSR223TestElements.java      |   6 +
 .../java/org/apache/jmeter/gui/action/Copy.java    |   3 +
 .../jmeter/gui/action/CreateFunctionDialog.java    |   3 +
 .../java/org/apache/jmeter/gui/action/Cut.java     |   3 +
 .../org/apache/jmeter/gui/action/Duplicate.java    |   3 +
 .../org/apache/jmeter/gui/action/EditCommand.java  |   3 +
 .../apache/jmeter/gui/action/EnableComponent.java  |   3 +
 .../org/apache/jmeter/gui/action/ExitCommand.java  |   3 +
 .../java/org/apache/jmeter/gui/action/Help.java    |   3 +
 .../java/org/apache/jmeter/gui/action/Load.java    |   2 +
 .../jmeter/gui/action/LoadRecentProject.java       |   3 +
 .../apache/jmeter/gui/action/LogLevelCommand.java  |   3 +
 .../gui/action/LoggerPanelEnableDisable.java       |   3 +
 .../jmeter/gui/action/LookAndFeelCommand.java      |   2 +
 .../java/org/apache/jmeter/gui/action/Move.java    |   3 +
 .../apache/jmeter/gui/action/OpenLinkAction.java   |   3 +
 .../java/org/apache/jmeter/gui/action/Paste.java   |   3 +
 .../org/apache/jmeter/gui/action/RemoteStart.java  |   3 +
 .../java/org/apache/jmeter/gui/action/Remove.java  |   3 +
 .../jmeter/gui/action/ResetSearchCommand.java      |   3 +
 .../java/org/apache/jmeter/gui/action/Restart.java |   6 +
 .../apache/jmeter/gui/action/RevertProject.java    |   3 +
 .../jmeter/gui/action/SSLManagerCommand.java       |   3 +
 .../java/org/apache/jmeter/gui/action/Save.java    |   3 +
 .../apache/jmeter/gui/action/SaveBeforeRun.java    |   3 +
 .../org/apache/jmeter/gui/action/SaveGraphics.java |   3 +
 .../apache/jmeter/gui/action/SchematicView.java    |   6 +
 .../jmeter/gui/action/SearchTreeCommand.java       |   3 +
 .../java/org/apache/jmeter/gui/action/Start.java   |   3 +
 .../apache/jmeter/gui/action/StopStoppables.java   |   3 +
 .../apache/jmeter/gui/action/TemplatesCommand.java |   3 +
 .../org/apache/jmeter/gui/action/UndoCommand.java  |   3 +
 .../java/org/apache/jmeter/gui/action/What.java    |   3 +
 .../org/apache/jmeter/gui/action/ZoomInOut.java    |   3 +
 .../org/apache/jmeter/gui/plugin/MenuCreator.java  |   3 +
 .../org/apache/jmeter/gui/util/JMeterMenuBar.java  |  47 ++-----
 .../org/apache/jmeter/gui/util/MenuFactory.java    |   2 +
 .../threads/RemoteThreadsLifeCycleListener.java    |   3 +
 .../jmeter/threads/RemoteThreadsListenerImpl.java  |  38 ++----
 .../java/org/apache/jmeter/util/JMeterUtils.java   |  75 +++++++++++
 .../java/org/apache/jorphan/test/AllTests.java     |   2 +
 .../java/org/apache/jmeter/junit/JMeterTest.java   |   1 +
 .../apache/jmeter/testbeans/gui/PackageTest.java   |   1 +
 .../apache/jorphan/reflect/TestClassFinder.java    |  17 ++-
 .../org/apache/jmeter/functions/BeanShell.java     |   3 +
 .../java/org/apache/jmeter/functions/CSVRead.java  |   3 +
 .../org/apache/jmeter/functions/ChangeCase.java    |   3 +
 .../org/apache/jmeter/functions/CharFunction.java  |   3 +
 .../jmeter/functions/DateTimeConvertFunction.java  |   3 +
 .../jmeter/functions/DigestEncodeFunction.java     |   3 +
 .../org/apache/jmeter/functions/EscapeHtml.java    |   3 +
 .../jmeter/functions/EscapeOroRegexpChars.java     |   3 +
 .../org/apache/jmeter/functions/EscapeXml.java     |   3 +
 .../org/apache/jmeter/functions/EvalFunction.java  |   3 +
 .../apache/jmeter/functions/EvalVarFunction.java   |   3 +
 .../org/apache/jmeter/functions/FileToString.java  |   3 +
 .../org/apache/jmeter/functions/FileWrapper.java   |   2 +
 .../java/org/apache/jmeter/functions/Groovy.java   |   3 +
 .../java/org/apache/jmeter/functions/IntSum.java   |   3 +
 .../org/apache/jmeter/functions/IsPropDefined.java |   3 +
 .../org/apache/jmeter/functions/IsVarDefined.java  |   3 +
 .../apache/jmeter/functions/IterationCounter.java  |   3 +
 .../org/apache/jmeter/functions/JavaScript.java    |   3 +
 .../org/apache/jmeter/functions/Jexl2Function.java |   3 +
 .../org/apache/jmeter/functions/Jexl3Function.java |   3 +
 .../org/apache/jmeter/functions/LogFunction.java   |   3 +
 .../org/apache/jmeter/functions/LogFunction2.java  |   3 +
 .../java/org/apache/jmeter/functions/LongSum.java  |   3 +
 .../org/apache/jmeter/functions/MachineIP.java     |   3 +
 .../org/apache/jmeter/functions/MachineName.java   |   3 +
 .../java/org/apache/jmeter/functions/Property.java |   3 +
 .../org/apache/jmeter/functions/Property2.java     |   3 +
 .../java/org/apache/jmeter/functions/Random.java   |   3 +
 .../org/apache/jmeter/functions/RandomDate.java    |   2 +
 .../jmeter/functions/RandomFromMultipleVars.java   |   3 +
 .../org/apache/jmeter/functions/RandomString.java  |   3 +
 .../org/apache/jmeter/functions/RegexFunction.java |   3 +
 .../org/apache/jmeter/functions/SamplerName.java   |   3 +
 .../org/apache/jmeter/functions/SetProperty.java   |   3 +
 .../org/apache/jmeter/functions/SplitFunction.java |   3 +
 .../apache/jmeter/functions/StringFromFile.java    |   3 +
 .../org/apache/jmeter/functions/StringToFile.java  |   3 +
 .../org/apache/jmeter/functions/TestPlanName.java  |   3 +
 .../apache/jmeter/functions/ThreadGroupName.java   |   3 +
 .../org/apache/jmeter/functions/ThreadNumber.java  |   3 +
 .../org/apache/jmeter/functions/TimeFunction.java  |   3 +
 .../org/apache/jmeter/functions/TimeShift.java     |   2 +
 .../java/org/apache/jmeter/functions/UnEscape.java |   3 +
 .../org/apache/jmeter/functions/UnEscapeHtml.java  |   3 +
 .../org/apache/jmeter/functions/UrlDecode.java     |   3 +
 .../org/apache/jmeter/functions/UrlEncode.java     |   3 +
 .../java/org/apache/jmeter/functions/Uuid.java     |   3 +
 .../java/org/apache/jmeter/functions/Variable.java |   3 +
 .../java/org/apache/jmeter/functions/XPath.java    |   3 +
 .../jmeter/functions/FunctionServicesTest.kt       |  43 +++++++
 .../org/apache/jorphan/reflect/ClassFinder.java    | 132 +++++++++++++++++++-
 .../CollectServiceLoadExceptionHandler.java}       |  29 ++---
 .../reflect/IgnoreServiceLoadExceptionHandler.java |  12 +-
 .../org/apache/jorphan/reflect/JMeterService.java  |  39 ++++++
 .../LogAndIgnoreServiceLoadExceptionHandler.java   |  54 ++++++++
 .../RethrowServiceLoadExceptionHandler.java}       |  23 ++--
 .../reflect/ServiceLoadExceptionHandler.java}      |  35 +++---
 .../jorphan/reflect/ServiceLoadFailure.java}       |  38 ++++--
 .../http/gui/action/ParseCurlCommandAction.java    |   7 ++
 .../protocol/http/proxy/DefaultSamplerCreator.java |   2 +
 .../jmeter/protocol/http/proxy/SamplerCreator.java |   2 +
 .../protocol/http/proxy/SamplerCreatorFactory.java |  56 +++------
 .../http/sampler/AccessLogSamplerBeanInfo.java     | 138 +++++++++++----------
 .../protocol/http/util/accesslog/Filter.java       |   2 +
 .../protocol/http/util/accesslog/LogFilter.java    |   3 +
 .../protocol/http/util/accesslog/LogParser.java    |   2 +
 .../util/accesslog/OrderPreservingLogParser.java   |   3 +
 .../http/util/accesslog/SessionFilter.java         |   3 +
 .../http/util/accesslog/SharedTCLogParser.java     |   3 +
 .../protocol/http/util/accesslog/TCLogParser.java  |   3 +
 .../protocol/http/visualizers/RequestViewHTTP.java |   3 +
 .../protocol/java/config/gui/JavaConfigGui.java    |  29 ++---
 .../protocol/java/sampler/JavaSamplerClient.java   |   2 +
 .../apache/jmeter/protocol/java/test/JavaTest.java |   5 +-
 .../jmeter/protocol/java/test/SleepTest.java       |   4 +
 .../java/control/gui/JUnitTestSamplerGui.java      |  12 +-
 .../test-services/build.gradle.kts                 |   8 +-
 .../apache/jmeter/util/services/AbstractService.kt |  12 +-
 .../services/ServiceNotImplementingInterface.kt    |  12 +-
 .../util/services/ServiceThrowingException.kt}     |  28 ++---
 .../util/services/ServiceWithPrivateConstructor.kt |  12 +-
 .../apache/jmeter/util/services/WorkableService.kt |  18 +--
 .../apache/jmeter/util/services/loadServices.kt}   |  37 ++----
 .../src/test/kotlin/ServiceLoaderTest.kt           |  98 +++++++++++++++
 xdocs/changes.xml                                  |   1 +
 xdocs/usermanual/jmeter_tutorial.xml               |  54 +++++++-
 186 files changed, 1282 insertions(+), 448 deletions(-)

diff --git a/build-logic/jvm/build.gradle.kts b/build-logic/jvm/build.gradle.kts
index 0c8b9388a6..7044d76d98 100644
--- a/build-logic/jvm/build.gradle.kts
+++ b/build-logic/jvm/build.gradle.kts
@@ -24,6 +24,7 @@ dependencies {
     api(projects.verification)
     api("com.github.vlsi.crlf:com.github.vlsi.crlf.gradle.plugin:1.88")
     api("com.github.vlsi.gradle-extensions:com.github.vlsi.gradle-extensions.gradle.plugin:1.88")
-    api("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.21")
+    api("org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:1.8.21")
+    api("org.jetbrains.kotlin.kapt:org.jetbrains.kotlin.kapt.gradle.plugin:1.8.21")
     api("org.jetbrains.dokka:org.jetbrains.dokka.gradle.plugin:1.8.10")
 }
diff --git a/build-logic/jvm/src/main/kotlin/build-logic.kotlin.gradle.kts b/build-logic/jvm/src/main/kotlin/build-logic.autoservice.gradle.kts
similarity index 53%
copy from build-logic/jvm/src/main/kotlin/build-logic.kotlin.gradle.kts
copy to build-logic/jvm/src/main/kotlin/build-logic.autoservice.gradle.kts
index 65cb342e6d..532256faa9 100644
--- a/build-logic/jvm/src/main/kotlin/build-logic.kotlin.gradle.kts
+++ b/build-logic/jvm/src/main/kotlin/build-logic.autoservice.gradle.kts
@@ -16,36 +16,35 @@
  */
 
 import com.github.vlsi.gradle.dsl.configureEach
-import com.github.vlsi.gradle.properties.dsl.props
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("java-library")
-    id("build-logic.java")
-    id("build-logic.test-base")
-    id("com.github.autostyle")
-    kotlin("jvm")
 }
 
 dependencies {
-    testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
+    annotationProcessor("com.google.auto.service:auto-service")
+    compileOnlyApi("com.google.auto.service:auto-service-annotations")
 }
 
-val String.v: String get() = rootProject.extra["$this.version"] as String
+tasks.configureEach<JavaCompile> {
+    // Verify @AutoService annotations
+    options.compilerArgs.add("-Averify=true")
+}
+
+plugins.withId("org.jetbrains.kotlin.jvm") {
+    apply(plugin = "org.jetbrains.kotlin.kapt")
 
-kotlin {
-    // Require explicit access modifiers and require explicit types for public APIs.
-    // See https://kotlinlang.org/docs/whatsnew14.html#explicit-api-mode-for-library-authors
-    if (props.bool("kotlin.explicitApi", default = true)) {
-        explicitApi()
+    dependencies {
+        "kapt"("com.google.auto.service:auto-service")
+        // Unfortunately, plugins.withId("..kapt") can't be used as kapt plugin adds configuration via plugins.withId
+        findProject(":src:bom-thirdparty")?.let {
+            "kapt"(platform(it))
+        }
     }
 }
 
-tasks.configureEach<KotlinCompile> {
-    kotlinOptions {
-        if (!name.startsWith("compileTest")) {
-            apiVersion = "kotlin.api".v
-        }
-        jvmTarget = java.targetCompatibility.toString()
+tasks.configureEach<Jar> {
+    manifest {
+        attributes["JMeter-Skip-Class-Scanning"] = "true"
     }
 }
diff --git a/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts b/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts
index 534f894dfc..b2d6967d71 100644
--- a/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts
+++ b/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts
@@ -18,4 +18,5 @@
 plugins {
     id("build-logic.java")
     id("java-library")
+    id("build-logic.autoservice")
 }
diff --git a/build-logic/jvm/src/main/kotlin/build-logic.java.gradle.kts b/build-logic/jvm/src/main/kotlin/build-logic.java.gradle.kts
index 175c9740af..3f4a60691f 100644
--- a/build-logic/jvm/src/main/kotlin/build-logic.java.gradle.kts
+++ b/build-logic/jvm/src/main/kotlin/build-logic.java.gradle.kts
@@ -59,6 +59,7 @@ dependencies {
     }
     findProject(":src:bom-thirdparty")?.let{
         api(platform(it))
+        annotationProcessor(platform(it))
     }
 }
 
diff --git a/build-logic/jvm/src/main/kotlin/build-logic.jvm-library.gradle.kts b/build-logic/jvm/src/main/kotlin/build-logic.jvm-library.gradle.kts
index cabe6e0282..7f5f0315fd 100644
--- a/build-logic/jvm/src/main/kotlin/build-logic.jvm-library.gradle.kts
+++ b/build-logic/jvm/src/main/kotlin/build-logic.jvm-library.gradle.kts
@@ -16,8 +16,7 @@
  */
 
 plugins {
-    id("build-logic.java")
-    id("java-library")
+    id("build-logic.java-library")
 }
 
 if (file("src/main/groovy").isDirectory || file("src/test/groovy").isDirectory) {
diff --git a/build-logic/jvm/src/main/kotlin/build-logic.kotlin.gradle.kts b/build-logic/jvm/src/main/kotlin/build-logic.kotlin.gradle.kts
index 65cb342e6d..59a63eb01d 100644
--- a/build-logic/jvm/src/main/kotlin/build-logic.kotlin.gradle.kts
+++ b/build-logic/jvm/src/main/kotlin/build-logic.kotlin.gradle.kts
@@ -25,6 +25,7 @@ plugins {
     id("build-logic.test-base")
     id("com.github.autostyle")
     kotlin("jvm")
+    kotlin("kapt") apply false
 }
 
 dependencies {
diff --git a/build-logic/root-build/build.gradle.kts b/build-logic/root-build/build.gradle.kts
index a467ad083a..5e3d3e23bd 100644
--- a/build-logic/root-build/build.gradle.kts
+++ b/build-logic/root-build/build.gradle.kts
@@ -27,4 +27,5 @@ dependencies {
     api("com.github.vlsi.gradle-extensions:com.github.vlsi.gradle-extensions.gradle.plugin:1.88")
     api("org.nosphere.apache.rat:org.nosphere.apache.rat.gradle.plugin:0.8.0")
     api("org.jetbrains.gradle.plugin.idea-ext:org.jetbrains.gradle.plugin.idea-ext.gradle.plugin:1.1.7")
+    api("org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:1.8.21")
 }
diff --git a/build-logic/root-build/src/main/kotlin/build-logic.root-build.gradle.kts b/build-logic/root-build/src/main/kotlin/build-logic.root-build.gradle.kts
index 2e75d5e448..7fcb96ff8d 100644
--- a/build-logic/root-build/src/main/kotlin/build-logic.root-build.gradle.kts
+++ b/build-logic/root-build/src/main/kotlin/build-logic.root-build.gradle.kts
@@ -26,6 +26,7 @@ plugins {
     id("com.github.vlsi.ide")
     id("org.nosphere.apache.rat")
     id("org.jetbrains.gradle.plugin.idea-ext")
+    kotlin("jvm") apply false
 }
 
 ide {
diff --git a/build.gradle.kts b/build.gradle.kts
index 91cb5261d7..50ac4136f7 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -61,6 +61,7 @@ val notPublishedProjects by extra {
         projects.src.release,
         projects.src.testkit,
         projects.src.testkitWiremock,
+        projects.src.testServices,
     ).mapTo(mutableSetOf()) { it.dependencyProject }
 }
 
@@ -86,7 +87,7 @@ publishedProjects.forEach {project ->
             throw IllegalStateException(
                 "Project ${project.path} is listed in publishedProjects, however it misses maven-publish plugin. " +
                     "Please add maven-publish plugin (e.g. replace build-logic.jvm-library with build-logic.jvm-published-library) or " +
-                    "move the project to the list of published ones"
+                    "move the project to the list of notPublishedProjects"
             )
         }
     }
diff --git a/gradle.properties b/gradle.properties
index 3801fc003f..eb14a43881 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -21,6 +21,8 @@ org.gradle.parallel=true
 org.gradle.caching=true
 #org.gradle.caching.debug=true
 
+kapt.include.compile.classpath=false
+
 # See https://github.com/gradle/gradle/pull/11358 , https://issues.apache.org/jira/browse/INFRA-14923
 # repository.apache.org does not yet support .sha256 and .sha512 checksums
 systemProp.org.gradle.internal.publish.checksums.insecure=true
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 2537f530c1..307ffd7b29 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -76,6 +76,7 @@ include(
     "src:release",
     "src:testkit",
     "src:testkit-wiremock",
+    "src:test-services",
     "src:dist",
     "src:dist-check"
 )
diff --git a/src/bom-thirdparty/build.gradle.kts b/src/bom-thirdparty/build.gradle.kts
index c7de118202..3483b7c2c5 100644
--- a/src/bom-thirdparty/build.gradle.kts
+++ b/src/bom-thirdparty/build.gradle.kts
@@ -42,6 +42,8 @@ dependencies {
 
         api("bsf:bsf:2.4.0")
         api("cglib:cglib-nodep:3.3.0")
+        api("com.google.auto.service:auto-service:1.0.1")
+        api("com.google.auto.service:auto-service-annotations:1.0.1")
         api("com.fasterxml.jackson.core:jackson-annotations:2.13.4")
         api("com.fasterxml.jackson.core:jackson-core:2.13.4")
         api("com.fasterxml.jackson.core:jackson-databind:2.13.4.2")
diff --git a/src/components/src/main/java/org/apache/jmeter/extractor/json/render/RenderAsJmesPathRenderer.java b/src/components/src/main/java/org/apache/jmeter/extractor/json/render/RenderAsJmesPathRenderer.java
index d2131a3350..45e1241d3c 100644
--- a/src/components/src/main/java/org/apache/jmeter/extractor/json/render/RenderAsJmesPathRenderer.java
+++ b/src/components/src/main/java/org/apache/jmeter/extractor/json/render/RenderAsJmesPathRenderer.java
@@ -19,6 +19,7 @@ package org.apache.jmeter.extractor.json.render;
 
 import org.apache.jmeter.extractor.json.jmespath.JMESPathCache;
 import org.apache.jmeter.util.JMeterUtils;
+import org.apache.jmeter.visualizers.ResultRenderer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -26,11 +27,13 @@ import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.auto.service.AutoService;
 
 /**
  * Implement ResultsRender for JMES Path tester
  * @since 5.2
  */
+@AutoService(ResultRenderer.class)
 public class RenderAsJmesPathRenderer extends AbstractRenderAsJsonRenderer {
     private static final Logger log = LoggerFactory.getLogger(RenderAsJmesPathRenderer.class);
     private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
diff --git a/src/components/src/main/java/org/apache/jmeter/extractor/json/render/RenderAsJsonRenderer.java b/src/components/src/main/java/org/apache/jmeter/extractor/json/render/RenderAsJsonRenderer.java
index e7d856db82..0aa1a01132 100644
--- a/src/components/src/main/java/org/apache/jmeter/extractor/json/render/RenderAsJsonRenderer.java
+++ b/src/components/src/main/java/org/apache/jmeter/extractor/json/render/RenderAsJsonRenderer.java
@@ -21,13 +21,17 @@ import java.util.List;
 
 import org.apache.jmeter.extractor.json.jsonpath.JSONManager;
 import org.apache.jmeter.util.JMeterUtils;
+import org.apache.jmeter.visualizers.ResultRenderer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Implement ResultsRender for JSON Path tester
  * @since 3.0
  */
+@AutoService(ResultRenderer.class)
 public class RenderAsJsonRenderer extends AbstractRenderAsJsonRenderer {
     private static final Logger log = LoggerFactory.getLogger(RenderAsJsonRenderer.class);
 
diff --git a/src/components/src/main/java/org/apache/jmeter/gui/action/ExportTransactionAndSamplerNames.java b/src/components/src/main/java/org/apache/jmeter/gui/action/ExportTransactionAndSamplerNames.java
index 093f217391..cc988f7d9f 100644
--- a/src/components/src/main/java/org/apache/jmeter/gui/action/ExportTransactionAndSamplerNames.java
+++ b/src/components/src/main/java/org/apache/jmeter/gui/action/ExportTransactionAndSamplerNames.java
@@ -53,10 +53,16 @@ import org.apache.jorphan.gui.ComponentUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Export transactions names for web report
  * @since 3.3
  */
+@AutoService({
+        Command.class,
+        MenuCreator.class
+})
 public class ExportTransactionAndSamplerNames extends AbstractAction implements MenuCreator {
     private static final Logger log = LoggerFactory.getLogger(ExportTransactionAndSamplerNames.class);
 
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsBoundaryExtractor.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsBoundaryExtractor.java
index e48002e246..236d2db7f3 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsBoundaryExtractor.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsBoundaryExtractor.java
@@ -41,9 +41,12 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.apache.jorphan.gui.GuiUtils;
 import org.apache.jorphan.gui.JLabeledTextField;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Implement ResultsRender for Boundary Extractor tester
  */
+@AutoService(ResultRenderer.class)
 public class RenderAsBoundaryExtractor implements ResultRenderer, ActionListener {
 
     private static final String BOUNDARY_EXTRACTOR_TESTER_COMMAND = "boundary_extractor_tester"; // $NON-NLS-1$
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsCssJQuery.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsCssJQuery.java
index e802246fea..aa0be670a0 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsCssJQuery.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsCssJQuery.java
@@ -49,10 +49,13 @@ import org.apache.jorphan.gui.JLabeledChoice;
 import org.apache.jorphan.gui.JLabeledTextField;
 import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Implement ResultsRender for CSS/JQuery tester
  * @since 2.10
  */
+@AutoService(ResultRenderer.class)
 public class RenderAsCssJQuery implements ResultRenderer, ActionListener {
 
     private static final String CSSJQUEY_TESTER_COMMAND = "cssjquery_tester"; // $NON-NLS-1$
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsDocument.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsDocument.java
index d7fde8f641..0bb01d437d 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsDocument.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsDocument.java
@@ -23,6 +23,9 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
+@AutoService(ResultRenderer.class)
 public class RenderAsDocument extends SamplerResultTab implements ResultRenderer {
 
     private static final Logger log = LoggerFactory.getLogger(RenderAsDocument.class);
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTML.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTML.java
index 043e7bc9f9..cd26262a15 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTML.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTML.java
@@ -33,6 +33,9 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
+@AutoService(ResultRenderer.class)
 public class RenderAsHTML extends SamplerResultTab implements ResultRenderer {
     private static final Logger log = LoggerFactory.getLogger(RenderAsHTML.class);
 
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLFormatted.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLFormatted.java
index cfbb7d208e..66b2b84ab4 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLFormatted.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLFormatted.java
@@ -21,6 +21,9 @@ import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.util.JMeterUtils;
 import org.jsoup.Jsoup;
 
+import com.google.auto.service.AutoService;
+
+@AutoService(ResultRenderer.class)
 public class RenderAsHTMLFormatted extends SamplerResultTab implements ResultRenderer {
 
     /** {@inheritDoc} */
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java
index d92f150120..4fa67e0569 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java
@@ -20,6 +20,9 @@ package org.apache.jmeter.visualizers;
 import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
+@AutoService(ResultRenderer.class)
 public class RenderAsHTMLWithEmbedded extends RenderAsHTML
     implements ResultRenderer {
 
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsJSON.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsJSON.java
index b1550a754b..b99d3d6c59 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsJSON.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsJSON.java
@@ -29,6 +29,9 @@ import net.minidev.json.JSONStyle;
 import net.minidev.json.parser.JSONParser;
 import net.minidev.json.parser.ParseException;
 
+import com.google.auto.service.AutoService;
+
+@AutoService(ResultRenderer.class)
 public class RenderAsJSON extends SamplerResultTab implements ResultRenderer {
     private static final String TAB_SEPARATOR = "    "; //$NON-NLS-1$
 
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsRegexp.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsRegexp.java
index 375d90299d..01d8358e03 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsRegexp.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsRegexp.java
@@ -50,9 +50,12 @@ import org.apache.oro.text.regex.PatternMatcherInput;
 import org.apache.oro.text.regex.Perl5Compiler;
 import org.apache.oro.text.regex.Perl5Matcher;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Implement ResultsRender for Regexp tester
  */
+@AutoService(ResultRenderer.class)
 public class RenderAsRegexp implements ResultRenderer, ActionListener {
 
     private static final String REGEXP_TESTER_COMMAND = "regexp_tester"; // $NON-NLS-1$
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsText.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsText.java
index 34616fe982..5d315677a5 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsText.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsText.java
@@ -20,6 +20,9 @@ package org.apache.jmeter.visualizers;
 import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
+@AutoService(ResultRenderer.class)
 public class RenderAsText extends SamplerResultTab implements ResultRenderer {
 
     /** {@inheritDoc} */
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXML.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXML.java
index 5c8b6af25b..beb976f2ad 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXML.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXML.java
@@ -53,6 +53,9 @@ import org.w3c.dom.NodeList;
 import org.w3c.tidy.Tidy;
 import org.xml.sax.SAXException;
 
+import com.google.auto.service.AutoService;
+
+@AutoService(ResultRenderer.class)
 public class RenderAsXML extends SamplerResultTab
     implements ResultRenderer {
 
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath.java
index df39c62e71..6058ce7ca3 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath.java
@@ -59,10 +59,13 @@ import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
 import org.xml.sax.SAXException;
 
+import com.google.auto.service.AutoService;
+
 
 /**
  * Implement ResultsRender for XPath tester
  */
+@AutoService(ResultRenderer.class)
 public class RenderAsXPath implements ResultRenderer, ActionListener {
 
     private static final Logger log = LoggerFactory.getLogger(RenderAsXPath.class);
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath2.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath2.java
index e36484bf90..42214a70af 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath2.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath2.java
@@ -48,9 +48,12 @@ import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Implement ResultsRender for XPath tester
  */
+@AutoService(ResultRenderer.class)
 public class RenderAsXPath2 implements ResultRenderer, ActionListener {
 
     private static final Logger log = LoggerFactory.getLogger(RenderAsXPath2.class);
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderInBrowser.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderInBrowser.java
index 575313c359..eab0bfd39a 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderInBrowser.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderInBrowser.java
@@ -27,6 +27,8 @@ import javax.swing.JPanel;
 import javax.swing.JProgressBar;
 import javax.swing.SwingUtilities;
 
+import com.google.auto.service.AutoService;
+
 import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.util.JMeterUtils;
 
@@ -43,6 +45,7 @@ import javafx.scene.web.WebView;
  * {@link ResultRenderer} implementation that uses JAVAFX WebEngine to render as browser do
  * @since 3.2
  */
+@AutoService(ResultRenderer.class)
 public class RenderInBrowser extends SamplerResultTab implements ResultRenderer {
 
     private JFXPanel jfxPanel;
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RequestPanel.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RequestPanel.java
index ac3a6a09e1..2b668a8ec3 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RequestPanel.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RequestPanel.java
@@ -18,11 +18,9 @@
 package org.apache.jmeter.visualizers;
 
 import java.awt.BorderLayout;
-import java.io.IOException;
 import java.util.ArrayDeque;
-import java.util.Collections;
 import java.util.Deque;
-import java.util.List;
+import java.util.ServiceLoader;
 
 import javax.swing.JPanel;
 import javax.swing.JTabbedPane;
@@ -30,6 +28,7 @@ import javax.swing.SwingConstants;
 
 import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.util.JMeterUtils;
+import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -52,37 +51,23 @@ public class RequestPanel {
      */
     public RequestPanel() {
         listRequestView = new ArrayDeque<>();
-        List<String> classesToAdd = Collections.<String> emptyList();
-        try {
-            classesToAdd = JMeterUtils.findClassesThatExtend(RequestView.class);
-        } catch (IOException e1) {
-            // ignored
-        }
         String rawTab = JMeterUtils.getResString(RequestViewRaw.KEY_LABEL); // $NON-NLS-1$
-        Object rawObject = null;
-        for (String clazz : classesToAdd) {
-            try {
-                // Instantiate requestview classes
-                final RequestView requestView = Class.forName(clazz)
-                        .asSubclass(RequestView.class)
-                        .getDeclaredConstructor().newInstance();
-                if (rawTab.equals(requestView.getLabel())) {
-                    rawObject = requestView; // use later
-                } else {
-                    listRequestView.add(requestView);
-                }
-            }
-            catch (NoClassDefFoundError e) {
-                log.error("Exception registering implementation: [{}] of interface: [{}], a dependency used by the plugin class is missing",
-                        clazz, RequestView.class, e);
-            } catch (Exception e) {
-                log.error("Exception registering implementation: [{}] of interface: [{}], a jar is probably missing",
-                        clazz, RequestView.class, e);
+        RequestView rawObject = null;
+        for (RequestView requestView : JMeterUtils.loadServicesAndScanJars(
+                RequestView.class,
+                ServiceLoader.load(RequestView.class),
+                Thread.currentThread().getContextClassLoader(),
+                new LogAndIgnoreServiceLoadExceptionHandler(log)
+        )) {
+            if (rawTab.equals(requestView.getLabel())) {
+                rawObject = requestView; // use later
+            } else {
+                listRequestView.add(requestView);
             }
         }
         // place raw tab in first position (first tab)
         if (rawObject != null) {
-            listRequestView.addFirst((RequestView) rawObject);
+            listRequestView.addFirst(rawObject);
         }
 
         // Prepare the Request tabbed pane
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RequestView.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RequestView.java
index 792f5e2ffc..e84cc4c038 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RequestView.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RequestView.java
@@ -19,12 +19,15 @@ package org.apache.jmeter.visualizers;
 
 import javax.swing.JPanel;
 
+import org.apache.jorphan.reflect.JMeterService;
+
 /**
  * Interface for request panel in View Results Tree
  * All classes which implements this interface is display
  * on bottom tab in request panel
  *
  */
+@JMeterService
 public interface RequestView {
 
     /**
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RequestViewRaw.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RequestViewRaw.java
index c721a152d5..b253e0381d 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RequestViewRaw.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RequestViewRaw.java
@@ -30,10 +30,13 @@ import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.util.JMeterUtils;
 import org.apache.jorphan.gui.GuiUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * (historical) Panel to view request data
  *
  */
+@AutoService(RequestView.class)
 public class RequestViewRaw implements RequestView {
 
     // Used by Request Panel
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/ResultRenderer.java b/src/components/src/main/java/org/apache/jmeter/visualizers/ResultRenderer.java
index 3f9e5f22b9..dba2ba3e58 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/ResultRenderer.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/ResultRenderer.java
@@ -22,11 +22,13 @@ import java.awt.Color;
 import javax.swing.JTabbedPane;
 
 import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jorphan.reflect.JMeterService;
 
 
 /**
  * Interface to results render
  */
+@JMeterService
 public interface ResultRenderer {
 
     void clearData();
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java b/src/components/src/main/java/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java
index 4f44ca2219..5ec83c5e5c 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java
@@ -25,7 +25,6 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
-import java.io.IOException;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -36,6 +35,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Queue;
+import java.util.ServiceLoader;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -73,6 +73,7 @@ import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.util.JMeterUtils;
 import org.apache.jmeter.visualizers.gui.AbstractVisualizer;
 import org.apache.jorphan.gui.JMeterUIDefaults;
+import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler;
 import org.apache.jorphan.util.StringWrap;
 import org.apiguardian.api.API;
 import org.slf4j.Logger;
@@ -454,38 +455,24 @@ implements ActionListener, TreeSelectionListener, Clearable, ItemListener {
         selectRenderPanel.addActionListener(this);
 
         // if no results render in jmeter.properties, load Standard (default)
-        List<String> classesToAdd = Collections.<String>emptyList();
-        try {
-            classesToAdd = JMeterUtils.findClassesThatExtend(ResultRenderer.class);
-        } catch (IOException e1) {
-            // ignored
-        }
         String defaultRenderer = expandToClassname(".RenderAsText"); // $NON-NLS-1$
         if (VIEWERS_ORDER.length() > 0) {
             defaultRenderer = expandToClassname(VIEWERS_ORDER.split(",", 2)[0]);
         }
-        Object defaultObject = null;
-        Map<String, ResultRenderer> map = new HashMap<>(classesToAdd.size());
-        for (String clazz : classesToAdd) {
-            try {
-                // Instantiate render classes
-                final ResultRenderer renderer = Class.forName(clazz)
-                        .asSubclass(ResultRenderer.class)
-                        .getDeclaredConstructor().newInstance();
-                if (defaultRenderer.equals(clazz)) {
-                    defaultObject=renderer;
-                }
-                renderer.setBackgroundColor(getBackground());
-                map.put(renderer.getClass().getName(), renderer);
-            } catch (NoClassDefFoundError e) { // NOSONAR See bug 60583
-                if (e.getMessage() != null && e.getMessage().contains("javafx")) {
-                    log.info("Add JavaFX to your Java installation if you want to use renderer: {}", clazz);
-                } else {
-                    log.warn("Error loading result renderer: {}", clazz, e);
-                }
-            } catch (Exception e) {
-                log.warn("Error loading result renderer: {}", clazz, e);
+        ResultRenderer defaultObject = null;
+        Map<String, ResultRenderer> map = new HashMap<>();
+        for (ResultRenderer renderer : JMeterUtils.loadServicesAndScanJars(
+                ResultRenderer.class,
+                ServiceLoader.load(ResultRenderer.class),
+                Thread.currentThread().getContextClassLoader(),
+                new LogAndIgnoreServiceLoadExceptionHandler(log)
+        )) {
+            // Instantiate render classes
+            if (defaultRenderer.equals(renderer.getClass().getName())) {
+                defaultObject = renderer;
             }
+            renderer.setBackgroundColor(getBackground());
+            map.put(renderer.getClass().getName(), renderer);
         }
         if (VIEWERS_ORDER.length() > 0) {
             Arrays.stream(VIEWERS_ORDER.split(","))
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/AbstractBackendListenerClient.java b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/AbstractBackendListenerClient.java
index ec0a870e43..79cdd58a6e 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/AbstractBackendListenerClient.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/AbstractBackendListenerClient.java
@@ -24,6 +24,8 @@ import org.apache.jmeter.config.Arguments;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * An abstract implementation of the BackendListenerClient interface. This
  * implementation provides default implementations of most of the methods in the
@@ -49,6 +51,7 @@ import org.slf4j.LoggerFactory;
  * @see BackendListener#sampleOccurred(org.apache.jmeter.samplers.SampleEvent)
  * @since 2.13
  */
+@AutoService(BackendListenerClient.class)
 public abstract class AbstractBackendListenerClient implements BackendListenerClient {
 
     private static final Logger log = LoggerFactory.getLogger(AbstractBackendListenerClient.class);
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/BackendListenerClient.java b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/BackendListenerClient.java
index e3aee0aa10..b8ab2dc4e7 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/BackendListenerClient.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/BackendListenerClient.java
@@ -21,6 +21,7 @@ import java.util.List;
 
 import org.apache.jmeter.config.Arguments;
 import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jorphan.reflect.JMeterService;
 
 /**
  * This interface defines the interactions between the {@link BackendListener}
@@ -63,6 +64,7 @@ import org.apache.jmeter.samplers.SampleResult;
  *
  * @since 2.13
  */
+@JMeterService
 public interface BackendListenerClient {
 
     /**
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/BackendListenerGui.java b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/BackendListenerGui.java
index dc98715df5..2e79427ed1 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/BackendListenerGui.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/BackendListenerGui.java
@@ -20,11 +20,10 @@ package org.apache.jmeter.visualizers.backend;
 import java.awt.BorderLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
+import java.util.ServiceLoader;
 import java.util.Set;
 
 import javax.swing.ComboBoxModel;
@@ -33,7 +32,6 @@ import javax.swing.JLabel;
 import javax.swing.JPanel;
 import javax.swing.JTextField;
 
-import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.jmeter.config.Argument;
 import org.apache.jmeter.config.Arguments;
@@ -45,7 +43,7 @@ import org.apache.jmeter.testelement.TestElement;
 import org.apache.jmeter.testelement.property.JMeterProperty;
 import org.apache.jmeter.util.JMeterUtils;
 import org.apache.jmeter.visualizers.gui.AbstractListenerGui;
-import org.apache.jorphan.reflect.ClassFinder;
+import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -77,7 +75,6 @@ public class BackendListenerGui extends AbstractListenerGui implements ActionLis
     /** The current className of the Backend listener **/
     private String className;
 
-
     /**
      * Create a new BackendListenerGui as a standalone component.
      */
@@ -117,25 +114,18 @@ public class BackendListenerGui extends AbstractListenerGui implements ActionLis
      * @return a panel containing the relevant components
      */
     private JPanel createClassnamePanel() {
-        List<String> possibleClasses = new ArrayList<>();
-
-        try {
-            // Find all the classes which implement the BackendListenerClient
-            // interface.
-            possibleClasses = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(),
-                    new Class[] { BackendListenerClient.class });
-
-            // Remove the BackendListener class from the list since it only
-            // implements the interface for error conditions.
-
-            possibleClasses.remove(BackendListener.class.getName() + "$ErrorBackendListenerClient");
-        } catch (Exception e) {
-            log.debug("Exception getting interfaces.", e);
-        }
-
         JLabel label = new JLabel(JMeterUtils.getResString("backend_listener_classname")); // $NON-NLS-1$
 
-        classnameCombo = new JComboBox<>(possibleClasses.toArray(ArrayUtils.EMPTY_STRING_ARRAY));
+        String[] listenerClasses = JMeterUtils.loadServicesAndScanJars(
+                        BackendListenerClient.class,
+                        ServiceLoader.load(BackendListenerClient.class),
+                        Thread.currentThread().getContextClassLoader(),
+                        new LogAndIgnoreServiceLoadExceptionHandler(log)
+                ).stream()
+                .map(s -> s.getClass().getName())
+                .sorted()
+                .toArray(String[]::new);
+        classnameCombo = new JComboBox<>(listenerClasses);
         classnameCombo.addActionListener(this);
         classnameCombo.setEditable(false);
         label.setLabelFor(classnameCombo);
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java
index 16d8b4a7db..36ad35ec00 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java
@@ -37,17 +37,21 @@ import org.apache.jmeter.config.Arguments;
 import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.util.JMeterUtils;
 import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient;
+import org.apache.jmeter.visualizers.backend.BackendListenerClient;
 import org.apache.jmeter.visualizers.backend.BackendListenerContext;
 import org.apache.jmeter.visualizers.backend.SamplerMetric;
 import org.apache.jmeter.visualizers.backend.UserMetric;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Graphite based Listener using Pickle Protocol
  * @see <a href="http://graphite.readthedocs.org/en/latest/overview.html">Graphite Overview</a>
  * @since 2.13
  */
+@AutoService(BackendListenerClient.class)
 public class GraphiteBackendListenerClient extends AbstractBackendListenerClient implements Runnable {
 
     //+ Argument names
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/influxdb/InfluxDBRawBackendListenerClient.java b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/influxdb/InfluxDBRawBackendListenerClient.java
index f959691f17..57be7a8a39 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/influxdb/InfluxDBRawBackendListenerClient.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/influxdb/InfluxDBRawBackendListenerClient.java
@@ -29,6 +29,8 @@ import org.apache.jmeter.visualizers.backend.BackendListenerContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Implementation of {@link BackendListenerClient} to write the response times
  * of every sample to InfluxDB. If more "raw" information is required in InfluxDB
@@ -38,6 +40,7 @@ import org.slf4j.LoggerFactory;
  *
  * @since 5.3
  */
+@AutoService(BackendListenerClient.class)
 public class InfluxDBRawBackendListenerClient implements BackendListenerClient {
 
     private static final Logger log = LoggerFactory.getLogger(InfluxDBRawBackendListenerClient.class);
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/influxdb/InfluxdbBackendListenerClient.java b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/influxdb/InfluxdbBackendListenerClient.java
index 0dc8d4f7fc..11a8739b8c 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/influxdb/InfluxdbBackendListenerClient.java
+++ b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/influxdb/InfluxdbBackendListenerClient.java
@@ -36,6 +36,7 @@ import org.apache.jmeter.config.Arguments;
 import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.util.JMeterUtils;
 import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient;
+import org.apache.jmeter.visualizers.backend.BackendListenerClient;
 import org.apache.jmeter.visualizers.backend.BackendListenerContext;
 import org.apache.jmeter.visualizers.backend.ErrorMetric;
 import org.apache.jmeter.visualizers.backend.SamplerMetric;
@@ -43,12 +44,15 @@ import org.apache.jmeter.visualizers.backend.UserMetric;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Implementation of {@link AbstractBackendListenerClient} to write to InfluxDB
  * using a custom schema; since JMeter 5.2, this also support the InfluxDB v2.
  *
  * @since 3.2
  */
+@AutoService(BackendListenerClient.class)
 public class InfluxdbBackendListenerClient extends AbstractBackendListenerClient implements Runnable {
 
     private static final Logger log = LoggerFactory.getLogger(InfluxdbBackendListenerClient.class);
diff --git a/src/core/src/main/java/org/apache/jmeter/engine/util/CompoundVariable.java b/src/core/src/main/java/org/apache/jmeter/engine/util/CompoundVariable.java
index 1059d4675d..b298fde061 100644
--- a/src/core/src/main/java/org/apache/jmeter/engine/util/CompoundVariable.java
+++ b/src/core/src/main/java/org/apache/jmeter/engine/util/CompoundVariable.java
@@ -22,6 +22,7 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.ServiceLoader;
 
 import org.apache.jmeter.functions.Function;
 import org.apache.jmeter.functions.InvalidVariableException;
@@ -30,7 +31,7 @@ import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.threads.JMeterContext;
 import org.apache.jmeter.threads.JMeterContextService;
 import org.apache.jmeter.util.JMeterUtils;
-import org.apache.jorphan.reflect.ClassFinder;
+import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -69,15 +70,15 @@ public class CompoundVariable implements Function {
                 log.info("Note: Function class names must not contain the string: '{}'", notContain);
             }
 
-            List<String> classes = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(),
-                    new Class[] { Function.class }, true, contain, notContain);
-            for (String clazzName : classes) {
-                Function tempFunc = Class.forName(clazzName)
-                        .asSubclass(Function.class)
-                        .getDeclaredConstructor().newInstance();
-                String referenceKey = tempFunc.getReferenceKey();
+            for (Function function : JMeterUtils.loadServicesAndScanJars(
+                    Function.class,
+                    ServiceLoader.load(Function.class),
+                    Thread.currentThread().getContextClassLoader(),
+                    new LogAndIgnoreServiceLoadExceptionHandler(log)
+            )) {
+                String referenceKey = function.getReferenceKey();
                 if (referenceKey.length() > 0) { // ignore self
-                    functions.put(referenceKey, tempFunc.getClass());
+                    functions.put(referenceKey, function.getClass());
                 }
             }
 
diff --git a/src/core/src/main/java/org/apache/jmeter/functions/Function.java b/src/core/src/main/java/org/apache/jmeter/functions/Function.java
index ff27cd5595..fb4d759d10 100644
--- a/src/core/src/main/java/org/apache/jmeter/functions/Function.java
+++ b/src/core/src/main/java/org/apache/jmeter/functions/Function.java
@@ -23,18 +23,20 @@ import java.util.List;
 import org.apache.jmeter.engine.util.CompoundVariable;
 import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.samplers.Sampler;
+import org.apache.jorphan.reflect.JMeterService;
 
 /**
  * Methods that a function must implement
  */
+@JMeterService
 public interface Function {
     /**
      * Given the previous SampleResult and the current Sampler, return a string
      * to use as a replacement value for the function call. Assume
      * "setParameter" was previously called.
      *
-     * This method must be threadsafe - multiple threads will be using the same
-     * object.
+     * <p>This method must be thread-safe - multiple threads will be using the same
+     * object.</p>
      * @param previousResult The previous {@link SampleResult}
      * @param currentSampler The current {@link Sampler}
      * @return The replacement value, which was generated by the function
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/HtmlReportAction.java b/src/core/src/main/java/org/apache/jmeter/gui/HtmlReportAction.java
index cecd111bdb..42e1e175ef 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/HtmlReportAction.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/HtmlReportAction.java
@@ -30,9 +30,16 @@ import org.apache.jmeter.exceptions.IllegalUserActionException;
 import org.apache.jmeter.gui.action.AbstractAction;
 import org.apache.jmeter.gui.action.ActionNames;
 import org.apache.jmeter.gui.action.ActionRouter;
+import org.apache.jmeter.gui.action.Command;
 import org.apache.jmeter.gui.plugin.MenuCreator;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
+@AutoService({
+        Command.class,
+        MenuCreator.class
+})
 public class HtmlReportAction extends AbstractAction implements MenuCreator {
     private static final Set<String> commands = new HashSet<>();
     private HtmlReportUI htmlReportPanel;
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/AboutCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/AboutCommand.java
index d200380078..4be2b38389 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/AboutCommand.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/AboutCommand.java
@@ -42,10 +42,13 @@ import org.apache.jmeter.gui.GuiPackage;
 import org.apache.jmeter.gui.util.EscapeDialog;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * About Command. It may be extended in the future to add a list of installed
  * protocols, config options, etc.
  */
+@AutoService(Command.class)
 public class AboutCommand extends AbstractAction {
     private static final Set<String> commandSet;
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/ActionRouter.java b/src/core/src/main/java/org/apache/jmeter/gui/action/ActionRouter.java
index 53110bd3a9..49b66206c1 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/ActionRouter.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/ActionRouter.java
@@ -309,6 +309,7 @@ public final class ActionRouter implements ActionListener {
         }
     }
 
+    @SuppressWarnings("deprecation")
     private static List<String> findClassesThatExtend(String className, String excluding, String[] searchPath) throws IOException, ClassNotFoundException {
 
         return ClassFinder.findClassesThatExtend(
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/AddParent.java b/src/core/src/main/java/org/apache/jmeter/gui/action/AddParent.java
index 32dad85108..4e8b9b8b42 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/AddParent.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/AddParent.java
@@ -28,9 +28,12 @@ import org.apache.jmeter.testelement.TestElement;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Implements the Add Parent menu command
  */
+@AutoService(Command.class)
 public class AddParent extends AbstractAction {
     private static final Logger log = LoggerFactory.getLogger(AddParent.class);
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/AddThinkTimeBetweenEachStep.java b/src/core/src/main/java/org/apache/jmeter/gui/action/AddThinkTimeBetweenEachStep.java
index b7966e7946..44ec34fc65 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/AddThinkTimeBetweenEachStep.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/AddThinkTimeBetweenEachStep.java
@@ -33,10 +33,13 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Add ThinkTime (TestAction + UniformRandomTimer)
  * @since 3.2
  */
+@AutoService(Command.class)
 public class AddThinkTimeBetweenEachStep extends AbstractAction {
     private static final Logger log = LoggerFactory.getLogger(AddThinkTimeBetweenEachStep.class);
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/AddToTree.java b/src/core/src/main/java/org/apache/jmeter/gui/action/AddToTree.java
index 8de19b9804..ecd423060c 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/AddToTree.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/AddToTree.java
@@ -32,6 +32,9 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
+@AutoService(Command.class)
 public class AddToTree extends AbstractAction {
     private static final Logger log = LoggerFactory.getLogger(AddToTree.class);
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/ApplyNamingConvention.java b/src/core/src/main/java/org/apache/jmeter/gui/action/ApplyNamingConvention.java
index 2cda16bb13..01ea85f269 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/ApplyNamingConvention.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/ApplyNamingConvention.java
@@ -32,10 +32,13 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Allows to apply naming convention on nodes
  * @since 3.2
  */
+@AutoService(Command.class)
 public class ApplyNamingConvention extends AbstractAction {
     private static final Logger log = LoggerFactory.getLogger(ApplyNamingConvention.class);
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/ChangeLanguage.java b/src/core/src/main/java/org/apache/jmeter/gui/action/ChangeLanguage.java
index 1cb2807381..2d72b1f041 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/ChangeLanguage.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/ChangeLanguage.java
@@ -28,9 +28,12 @@ import org.apache.jorphan.util.JMeterError;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Change language
  */
+@AutoService(Command.class)
 public class ChangeLanguage extends AbstractActionWithNoRunningTest {
     private static final Set<String> commands = new HashSet<>();
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/ChangeParent.java b/src/core/src/main/java/org/apache/jmeter/gui/action/ChangeParent.java
index 3396c51fed..8c9729416a 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/ChangeParent.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/ChangeParent.java
@@ -38,9 +38,12 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Allows to change Controller implementation
  */
+@AutoService(Command.class)
 public class ChangeParent extends AbstractAction {
     private static final Logger log = LoggerFactory.getLogger(ChangeParent.class);
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/CheckDirty.java b/src/core/src/main/java/org/apache/jmeter/gui/action/CheckDirty.java
index 3ab37b81e5..eb3885d32c 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/CheckDirty.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/CheckDirty.java
@@ -33,10 +33,13 @@ import org.apache.jorphan.collections.ListedHashTree;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Check if the TestPlan has been changed since it was last saved
  *
  */
+@AutoService(Command.class)
 public class CheckDirty extends AbstractAction implements HashTreeTraverser, ActionListener {
     private static final Logger log = LoggerFactory.getLogger(CheckDirty.class);
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Clear.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Clear.java
index 1f6964d0b8..a3f3a3998b 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/Clear.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Clear.java
@@ -28,12 +28,15 @@ import org.apache.jmeter.samplers.Clearable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Handles the following actions:
  * - Clear (Data)
  * - Clear All (Data)
  * - Reset (Clear GUI)
  */
+@AutoService(Command.class)
 public class Clear extends AbstractAction {
     private static final Logger log = LoggerFactory.getLogger(Clear.class);
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Close.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Close.java
index 405f39bb8b..a0c8348ed4 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/Close.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Close.java
@@ -29,11 +29,14 @@ import org.apache.jmeter.gui.util.FocusRequester;
 import org.apache.jmeter.services.FileServer;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * This command clears the existing test plan, allowing the creation of a New
  * test plan.
  *
  */
+@AutoService(Command.class)
 public class Close extends AbstractActionWithNoRunningTest {
 
     private static final Set<String> commands = new HashSet<>();
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/CollapseExpand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/CollapseExpand.java
index 1008ef763c..72da59d65f 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/CollapseExpand.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/CollapseExpand.java
@@ -25,10 +25,13 @@ import javax.swing.JTree;
 
 import org.apache.jmeter.gui.GuiPackage;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Processes the Collapse All and Expand All options.
  *
  */
+@AutoService(Command.class)
 public class CollapseExpand extends AbstractAction {
 
     private static final Set<String> commands = new HashSet<>();
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/CollapseExpandTreeBranch.java b/src/core/src/main/java/org/apache/jmeter/gui/action/CollapseExpandTreeBranch.java
index a70509d6fa..2b7af0730c 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/CollapseExpandTreeBranch.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/CollapseExpandTreeBranch.java
@@ -30,9 +30,12 @@ import org.apache.jmeter.gui.GuiPackage;
 import org.apache.jmeter.gui.tree.JMeterTreeListener;
 import org.apache.jmeter.gui.tree.JMeterTreeNode;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Processes the collapse and expand of a tree branch
  */
+@AutoService(Command.class)
 public class CollapseExpandTreeBranch extends AbstractAction {
     private static final Set<String> commands = new HashSet<>();
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/CompileJSR223TestElements.java b/src/core/src/main/java/org/apache/jmeter/gui/action/CompileJSR223TestElements.java
index f2dd30e985..7539d09d6d 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/CompileJSR223TestElements.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/CompileJSR223TestElements.java
@@ -38,10 +38,16 @@ import org.apache.jorphan.collections.HashTreeTraverser;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Compile JSR223 Test Element that use Compilable script language
  * @since 5.1
  */
+@AutoService({
+        Command.class,
+        MenuCreator.class
+})
 public class CompileJSR223TestElements extends AbstractAction implements MenuCreator {
     private static final Logger log = LoggerFactory.getLogger(CompileJSR223TestElements.class);
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Copy.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Copy.java
index 96165d07d7..3979512efe 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/Copy.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Copy.java
@@ -37,9 +37,12 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Implements the Copy menu command
  */
+@AutoService(Command.class)
 public class Copy extends AbstractAction {
     private static final Logger log = LoggerFactory.getLogger(Copy.class);
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/CreateFunctionDialog.java b/src/core/src/main/java/org/apache/jmeter/gui/action/CreateFunctionDialog.java
index 371a7e15a2..5e4d43a6f0 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/CreateFunctionDialog.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/CreateFunctionDialog.java
@@ -23,6 +23,9 @@ import java.util.Set;
 
 import org.apache.jmeter.functions.gui.FunctionHelper;
 
+import com.google.auto.service.AutoService;
+
+@AutoService(Command.class)
 public class CreateFunctionDialog extends AbstractAction {
 
     private static final Set<String> commands;
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Cut.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Cut.java
index 43e2ffbb09..a5a8f201a3 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/Cut.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Cut.java
@@ -24,9 +24,12 @@ import java.util.Set;
 import org.apache.jmeter.gui.GuiPackage;
 import org.apache.jmeter.gui.tree.JMeterTreeNode;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Implements the Cut menu item command
  */
+@AutoService(Command.class)
 public class Cut extends AbstractAction {
     private static final Set<String> commands = new HashSet<>();
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Duplicate.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Duplicate.java
index 683a4d8495..996cc149e8 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/Duplicate.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Duplicate.java
@@ -26,9 +26,12 @@ import org.apache.jmeter.gui.tree.JMeterTreeListener;
 import org.apache.jmeter.gui.tree.JMeterTreeModel;
 import org.apache.jmeter.gui.tree.JMeterTreeNode;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Implements the Duplicate menu command
  */
+@AutoService(Command.class)
 public class Duplicate extends AbstractAction {
 
     private static final HashSet<String> commands = new HashSet<>();
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/EditCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/EditCommand.java
index 705286de04..a6e8c8025e 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/EditCommand.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/EditCommand.java
@@ -25,9 +25,12 @@ import org.apache.jmeter.gui.GuiPackage;
 import org.apache.jmeter.gui.JMeterGUIComponent;
 import org.apache.jorphan.gui.ui.TextComponentUI;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Implements the Edit menu item.
  */
+@AutoService(Command.class)
 public class EditCommand extends AbstractAction {
     private static final Set<String> commands = new HashSet<>();
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/EnableComponent.java b/src/core/src/main/java/org/apache/jmeter/gui/action/EnableComponent.java
index 65f4407187..e5b9a225ed 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/EnableComponent.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/EnableComponent.java
@@ -26,9 +26,12 @@ import org.apache.jmeter.gui.tree.JMeterTreeNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Implements the Enable menu item.
  */
+@AutoService(Command.class)
 public class EnableComponent extends AbstractAction {
     private static final Logger log = LoggerFactory.getLogger(EnableComponent.class);
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/ExitCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/ExitCommand.java
index e9478721f0..3868e3c844 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/ExitCommand.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/ExitCommand.java
@@ -26,6 +26,9 @@ import javax.swing.JOptionPane;
 import org.apache.jmeter.gui.GuiPackage;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
+@AutoService(Command.class)
 public class ExitCommand extends AbstractActionWithNoRunningTest {
 
     private static final Set<String> commands = new HashSet<>();
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Help.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Help.java
index ab39e349ee..78f9767b5a 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/Help.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Help.java
@@ -35,9 +35,12 @@ import org.apache.jorphan.gui.ComponentUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Implements the Help menu item.
  */
+@AutoService(Command.class)
 public class Help extends AbstractAction {
     private static final Logger log = LoggerFactory.getLogger(Help.class);
     private static final boolean USE_LOCAL_HELP =
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Load.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Load.java
index 0755b855b6..10b997d141 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/Load.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Load.java
@@ -42,6 +42,7 @@ import org.apache.jorphan.collections.HashTree;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
 import com.thoughtworks.xstream.converters.ConversionException;
 import com.thoughtworks.xstream.io.StreamException;
 
@@ -49,6 +50,7 @@ import com.thoughtworks.xstream.io.StreamException;
  * Handles the Open (load a new file) and Merge commands.
  *
  */
+@AutoService(Command.class)
 public class Load extends AbstractActionWithNoRunningTest {
     private static final Logger log = LoggerFactory.getLogger(Load.class);
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/LoadRecentProject.java b/src/core/src/main/java/org/apache/jmeter/gui/action/LoadRecentProject.java
index 9b684ea07c..0b394a749b 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/LoadRecentProject.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/LoadRecentProject.java
@@ -34,10 +34,13 @@ import java.util.stream.IntStream;
 import javax.swing.JComponent;
 import javax.swing.JMenuItem;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Handles the loading of recent files, and also the content and
  * visibility of menu items for loading the recent files
  */
+@AutoService(Command.class)
 public class LoadRecentProject extends Load {
     /** Prefix for the user preference key */
     private static final String USER_PREFS_KEY = "recent_file_"; //$NON-NLS-1$
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/LogLevelCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/LogLevelCommand.java
index a21473f19c..b00337f774 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/LogLevelCommand.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/LogLevelCommand.java
@@ -29,10 +29,13 @@ import org.apiguardian.api.API;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Implements log level setting menu item.
  * @since 3.2
  */
+@AutoService(Command.class)
 public class LogLevelCommand extends AbstractAction {
 
     private static final Logger log = LoggerFactory.getLogger(LogLevelCommand.class);
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/LoggerPanelEnableDisable.java b/src/core/src/main/java/org/apache/jmeter/gui/action/LoggerPanelEnableDisable.java
index 7b7c30ee42..f3b3c3e1d9 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/LoggerPanelEnableDisable.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/LoggerPanelEnableDisable.java
@@ -26,10 +26,13 @@ import javax.swing.UIManager;
 
 import org.apache.jmeter.gui.GuiPackage;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Hide / unhide LoggerPanel.
  *
  */
+@AutoService(Command.class)
 public class LoggerPanelEnableDisable extends AbstractAction {
 
     private static final Set<String> commands = new HashSet<>();
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/LookAndFeelCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/LookAndFeelCommand.java
index 260c59f4f8..0821b8a05d 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/LookAndFeelCommand.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/LookAndFeelCommand.java
@@ -40,10 +40,12 @@ import org.apache.jorphan.gui.JFactory;
 import com.github.weisj.darklaf.LafManager;
 import com.github.weisj.darklaf.theme.DarculaTheme;
 import com.github.weisj.darklaf.theme.Theme;
+import com.google.auto.service.AutoService;
 
 /**
  * Implements the Look and Feel menu item.
  */
+@AutoService(Command.class)
 public class LookAndFeelCommand extends AbstractAction {
     private static final String JMETER_LAF = "jmeter.laf"; // $NON-NLS-1$
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Move.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Move.java
index 90350e2a31..d39bde84a5 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/Move.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Move.java
@@ -33,10 +33,13 @@ import org.apache.jmeter.gui.util.MenuFactory;
 import org.apache.jmeter.testelement.TestElement;
 import org.apache.jmeter.testelement.TestPlan;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Move a node up/down/left/right
  *
  */
+@AutoService(Command.class)
 public class Move extends AbstractAction {
     private static final Set<String> commands = new HashSet<>();
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/OpenLinkAction.java b/src/core/src/main/java/org/apache/jmeter/gui/action/OpenLinkAction.java
index 418b1b0d1b..be6ea6c430 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/OpenLinkAction.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/OpenLinkAction.java
@@ -29,6 +29,9 @@ import javax.swing.JOptionPane;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
+@AutoService(Command.class)
 public class OpenLinkAction extends AbstractAction {
 
     private static final Logger log = LoggerFactory.getLogger(OpenLinkAction.class);
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Paste.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Paste.java
index 6506d3faa5..cc6f670328 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/Paste.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Paste.java
@@ -33,9 +33,12 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Places a copied JMeterTreeNode under the selected node.
  */
+@AutoService(Command.class)
 public class Paste extends AbstractAction {
 
     private static final Logger log = LoggerFactory.getLogger(Paste.class);
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/RemoteStart.java b/src/core/src/main/java/org/apache/jmeter/gui/action/RemoteStart.java
index bb7e67148d..7ca26794da 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/RemoteStart.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/RemoteStart.java
@@ -33,6 +33,9 @@ import org.apache.jmeter.threads.RemoteThreadsListenerTestElement;
 import org.apache.jmeter.util.JMeterUtils;
 import org.apache.jorphan.collections.HashTree;
 
+import com.google.auto.service.AutoService;
+
+@AutoService(Command.class)
 public class RemoteStart extends AbstractAction {
 
     private static final String LOCAL_HOST = "127.0.0.1"; // NOSONAR $NON-NLS-1$
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Remove.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Remove.java
index 3e18f68e88..d3a95d8517 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/Remove.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Remove.java
@@ -29,9 +29,12 @@ import org.apache.jmeter.gui.tree.JMeterTreeNode;
 import org.apache.jmeter.testelement.TestElement;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Implements the Remove menu item.
  */
+@AutoService(Command.class)
 public class Remove extends AbstractAction {
 
     private static final Set<String> commands = new HashSet<>();
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/ResetSearchCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/ResetSearchCommand.java
index d10be439ac..32ae567bf0 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/ResetSearchCommand.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/ResetSearchCommand.java
@@ -26,9 +26,12 @@ import org.apache.jmeter.gui.GuiPackage;
 import org.apache.jmeter.gui.Searchable;
 import org.apache.jmeter.gui.tree.JMeterTreeNode;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Reset Search
  */
+@AutoService(Command.class)
 public class ResetSearchCommand extends AbstractAction {
 
     private static final Set<String> commands = new HashSet<>();
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Restart.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Restart.java
index 2b49b69498..e2fcc1d39f 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/Restart.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Restart.java
@@ -39,11 +39,17 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Restart JMeter
  * Based on https://dzone.com/articles/programmatically-restart-java
  * @since 5.0
  */
+@AutoService({
+        Command.class,
+        MenuCreator.class
+})
 public class Restart extends AbstractActionWithNoRunningTest implements MenuCreator {
     private static final Logger log = LoggerFactory.getLogger(Restart.class);
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/RevertProject.java b/src/core/src/main/java/org/apache/jmeter/gui/action/RevertProject.java
index 23f5e5bc30..f05ebba94b 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/RevertProject.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/RevertProject.java
@@ -27,10 +27,13 @@ import javax.swing.JOptionPane;
 import org.apache.jmeter.gui.GuiPackage;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Handles the Revert Project command.
  *
  */
+@AutoService(Command.class)
 public class RevertProject extends AbstractActionWithNoRunningTest {
     private static final Set<String> commands = new HashSet<>();
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/SSLManagerCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/SSLManagerCommand.java
index e3a1e47d5a..9b0ec71ec3 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/SSLManagerCommand.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/SSLManagerCommand.java
@@ -32,6 +32,8 @@ import org.apache.jmeter.gui.GuiPackage;
 import org.apache.jmeter.util.JMeterUtils;
 import org.apache.jmeter.util.SSLManager;
 
+import com.google.auto.service.AutoService;
+
 //
 /**
  * SSL Manager Command. The SSL Manager provides a mechanism to change your
@@ -52,6 +54,7 @@ import org.apache.jmeter.util.SSLManager;
  * already defined via the property.
  *
  */
+@AutoService(Command.class)
 public class SSLManagerCommand extends AbstractAction {
     private static final Set<String> commandSet;
     static {
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Save.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Save.java
index 9b6c6f4236..32e3846cac 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/Save.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Save.java
@@ -56,12 +56,15 @@ import org.apache.jorphan.collections.ListedHashTree;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Save the current test plan; implements:
  * Save
  * Save TestPlan As
  * Save (Selection) As
  */
+@AutoService(Command.class)
 public class Save extends AbstractAction {
     private static final Logger log = LoggerFactory.getLogger(Save.class);
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/SaveBeforeRun.java b/src/core/src/main/java/org/apache/jmeter/gui/action/SaveBeforeRun.java
index ac4e1929f0..39191e5296 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/SaveBeforeRun.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/SaveBeforeRun.java
@@ -23,11 +23,14 @@ import java.util.Set;
 
 import org.apache.jmeter.gui.GuiPackage;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Save Before Run Action To save test plan before GUI execution
  *
  * @since 4.0
  */
+@AutoService(Command.class)
 public class SaveBeforeRun extends AbstractAction {
     private static final Set<String> commands = new HashSet<>();
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/SaveGraphics.java b/src/core/src/main/java/org/apache/jmeter/gui/action/SaveGraphics.java
index 481809c44e..c73cabefc3 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/SaveGraphics.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/SaveGraphics.java
@@ -34,6 +34,8 @@ import org.apache.jmeter.save.SaveGraphicsService;
 import org.apache.jmeter.util.JMeterUtils;
 import org.apache.jmeter.visualizers.Printable;
 
+import com.google.auto.service.AutoService;
+
 /**
  * SaveGraphics action is meant to be a generic reusable Action. The class will
  * use GUIPackage to get the current gui. Once it does, it checks to see if the
@@ -42,6 +44,7 @@ import org.apache.jmeter.visualizers.Printable;
  * file if no extension is provided. If either .png or .tif is in the filename,
  * it will call SaveGraphicsService to save in the format.
  */
+@AutoService(Command.class)
 public class SaveGraphics extends AbstractAction {
 
     private static final Set<String> commands = new HashSet<>();
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/SchematicView.java b/src/core/src/main/java/org/apache/jmeter/gui/action/SchematicView.java
index 33ec59f944..8b0f1c9b64 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/SchematicView.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/SchematicView.java
@@ -47,10 +47,16 @@ import org.apache.jorphan.collections.HashTree;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Schematic view of Test Plan
  * @since 5.1
  */
+@AutoService({
+        Command.class,
+        MenuCreator.class
+})
 public class SchematicView extends AbstractAction implements MenuCreator {
     private static final Logger log = LoggerFactory.getLogger(SchematicView.class);
     private static final String DEFAULT_XSL_FILE =
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/SearchTreeCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/SearchTreeCommand.java
index 91f0c66452..0661c17a63 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/SearchTreeCommand.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/SearchTreeCommand.java
@@ -23,10 +23,13 @@ import java.util.Set;
 
 import javax.swing.JFrame;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Search nodes for a text
  * TODO Enhance search dialog to select kind of nodes ....
  */
+@AutoService(Command.class)
 public class SearchTreeCommand extends AbstractAction {
 
     private static final Set<String> commands = new HashSet<>();
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Start.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Start.java
index fee1a4baee..b31d1f3d73 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/Start.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Start.java
@@ -45,6 +45,8 @@ import org.apache.jorphan.collections.ListedHashTree;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Set of Actions to:
  * <ul>
@@ -57,6 +59,7 @@ import org.slf4j.LoggerFactory;
  *      <li>Validate a set of Thread Groups with/without sleeping on the timers depending on jmeter properties</li>
  * </ul>
  */
+@AutoService(Command.class)
 public class Start extends AbstractAction {
 
     private static final Logger log = LoggerFactory.getLogger(Start.class);
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/StopStoppables.java b/src/core/src/main/java/org/apache/jmeter/gui/action/StopStoppables.java
index c92b6a0916..f4f5ff60a6 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/StopStoppables.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/StopStoppables.java
@@ -26,10 +26,13 @@ import java.util.Set;
 import org.apache.jmeter.gui.GuiPackage;
 import org.apache.jmeter.gui.Stoppable;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Stops stopables (Proxy, Mirror)
  * @since 2.5.1
  */
+@AutoService(Command.class)
 public class StopStoppables extends AbstractAction implements ActionListener {
     private static final Set<String> commands = new HashSet<>();
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/TemplatesCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/TemplatesCommand.java
index 4896ae4e2a..58b6c58b26 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/TemplatesCommand.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/TemplatesCommand.java
@@ -21,10 +21,13 @@ import java.awt.event.ActionEvent;
 import java.util.HashSet;
 import java.util.Set;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Open Templates
  * @since 2.10
  */
+@AutoService(Command.class)
 public class TemplatesCommand extends AbstractActionWithNoRunningTest {
 
     private static final Set<String> commands = new HashSet<>();
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/UndoCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/UndoCommand.java
index a4a56f6637..fdae2d28ce 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/UndoCommand.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/UndoCommand.java
@@ -26,10 +26,13 @@ import org.apache.jmeter.exceptions.IllegalUserActionException;
 import org.apache.jmeter.gui.GuiPackage;
 import org.apache.jorphan.collections.HashTree;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Menu command to serve Undo/Redo
  * @since 2.12
  */
+@AutoService(Command.class)
 public class UndoCommand extends AbstractAction {
 
     private static final Set<String> commands = new HashSet<>();
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/What.java b/src/core/src/main/java/org/apache/jmeter/gui/action/What.java
index 1d202e30c2..08384fc606 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/What.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/What.java
@@ -35,6 +35,8 @@ import org.apache.logging.log4j.core.config.Configurator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  *
  * Debug class to show details of the currently selected object
@@ -43,6 +45,7 @@ import org.slf4j.LoggerFactory;
  * Also enables/disables debug for the test element.
  *
  */
+@AutoService(Command.class)
 public class What extends AbstractAction {
     private static final Logger log = LoggerFactory.getLogger(What.class);
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/ZoomInOut.java b/src/core/src/main/java/org/apache/jmeter/gui/action/ZoomInOut.java
index a6b496fe72..7209bfe740 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/action/ZoomInOut.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/action/ZoomInOut.java
@@ -24,10 +24,13 @@ import java.util.Set;
 import org.apache.jmeter.util.JMeterUtils;
 import org.apache.jorphan.gui.JMeterUIDefaults;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Zoom IN/OUT
  * @since 3.2
  */
+@AutoService(Command.class)
 public class ZoomInOut extends AbstractAction {
     private static final Set<String> commands = new HashSet<>();
 
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/plugin/MenuCreator.java b/src/core/src/main/java/org/apache/jmeter/gui/plugin/MenuCreator.java
index c4b902df6c..3d03fed1e1 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/plugin/MenuCreator.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/plugin/MenuCreator.java
@@ -21,9 +21,12 @@ import javax.swing.JMenu;
 import javax.swing.JMenuItem;
 import javax.swing.MenuElement;
 
+import org.apache.jorphan.reflect.JMeterService;
+
 /**
  * @since 2.10
  */
+@JMeterService
 public interface MenuCreator {
     enum MENU_LOCATION {
         FILE,
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/util/JMeterMenuBar.java b/src/core/src/main/java/org/apache/jmeter/gui/util/JMeterMenuBar.java
index 5072aa31ad..cef9cd2690 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/util/JMeterMenuBar.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/util/JMeterMenuBar.java
@@ -19,14 +19,13 @@ package org.apache.jmeter.gui.util;
 
 import java.awt.Component;
 import java.awt.event.KeyEvent;
-import java.io.IOException;
-import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import java.util.ServiceLoader;
 
 import javax.swing.ButtonGroup;
 import javax.swing.JCheckBoxMenuItem;
@@ -54,7 +53,7 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.apache.jmeter.util.LocaleChangeEvent;
 import org.apache.jmeter.util.LocaleChangeListener;
 import org.apache.jmeter.util.SSLManager;
-import org.apache.jorphan.reflect.ClassFinder;
+import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler;
 import org.apache.jorphan.util.JOrphanUtils;
 import org.apache.logging.log4j.Level;
 import org.slf4j.Logger;
@@ -92,7 +91,13 @@ public class JMeterMenuBar extends JMenuBar implements LocaleChangeListener {
     private JMenu remoteExit;
     private final Collection<JMenuItem> remoteEngineExit;
     private JMenu searchMenu;
-    private List<MenuCreator> menuCreators;
+    private final Collection<MenuCreator> menuCreators =
+            JMeterUtils.loadServicesAndScanJars(
+                    MenuCreator.class,
+                    ServiceLoader.load(MenuCreator.class),
+                    Thread.currentThread().getContextClassLoader(),
+                    new LogAndIgnoreServiceLoadExceptionHandler(log)
+            );
 
     public static final String SYSTEM_LAF = "System"; // $NON-NLS-1$
     public static final String CROSS_PLATFORM_LAF = "CrossPlatform"; // $NON-NLS-1$
@@ -192,9 +197,6 @@ public class JMeterMenuBar extends JMenuBar implements LocaleChangeListener {
      * should be defined in a file somewhere, but that is for later.
      */
     public void createMenuBar() {
-
-        this.menuCreators = findMenuCreators();
-
         makeFileMenu();
         makeEditMenu();
         makeRunMenu();
@@ -217,35 +219,6 @@ public class JMeterMenuBar extends JMenuBar implements LocaleChangeListener {
         this.add(helpMenu);
     }
 
-    private static List<MenuCreator> findMenuCreators() {
-        List<MenuCreator> creators = new ArrayList<>();
-        try {
-            List<String> listClasses = ClassFinder.findClassesThatExtend(
-                    JMeterUtils.getSearchPaths(),
-                    new Class[] {MenuCreator.class });
-            for (String strClassName : listClasses) {
-                try {
-                    log.debug("Loading menu creator class: {}", strClassName);
-                    Class<?> commandClass = Class.forName(strClassName);
-                    if (!Modifier.isAbstract(commandClass.getModifiers())) {
-                        log.debug("Instantiating: {}", commandClass);
-                        MenuCreator creator = (MenuCreator) commandClass.getDeclaredConstructor().newInstance();
-                        creators.add(creator);
-                    }
-                } catch (NoClassDefFoundError e) {
-                    log.error("Exception registering implementation: [{}] of interface: [{}], a dependency used by the plugin class is missing",
-                            strClassName, MenuCreator.class, e);
-                } catch (Exception e) {
-                    log.error("Exception registering implementation: [{}] of interface: [{}], a jar is probably missing",
-                            strClassName, MenuCreator.class, e);
-                }
-            }
-        } catch (IOException e) {
-            log.error("Exception finding implementations of {}", MenuCreator.class, e);
-        }
-        return creators;
-    }
-
     private void makeHelpMenu() {
         helpMenu = makeMenuRes("help",'H'); //$NON-NLS-1$
 
@@ -590,7 +563,7 @@ public class JMeterMenuBar extends JMenuBar implements LocaleChangeListener {
      * @param menuCreators
      * @param location
      */
-    private static void addPluginsMenuItems(JMenu menu, List<MenuCreator> menuCreators, MENU_LOCATION location) {
+    private static void addPluginsMenuItems(JMenu menu, Collection<MenuCreator> menuCreators, MENU_LOCATION location) {
         for (MenuCreator menuCreator : menuCreators) {
             JMenuItem[] menuItems = menuCreator.getMenuItemsAtLocation(location);
             if (menuItems.length != 0) {
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/util/MenuFactory.java b/src/core/src/main/java/org/apache/jmeter/gui/util/MenuFactory.java
index 4aafa80341..bc84feb916 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/util/MenuFactory.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/util/MenuFactory.java
@@ -124,6 +124,8 @@ public final class MenuFactory {
     private static void initializeMenus(
             Map<String, List<MenuInfo>> menus, Set<String> elementsToSkip) {
         try {
+            // TODO: migrate to ServiceLoader or something else
+            @SuppressWarnings("deprecation")
             List<String> guiClasses = ClassFinder
                     .findClassesThatExtend(
                             JMeterUtils.getSearchPaths(),
diff --git a/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java b/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java
index f2c8d99012..83dd5b71ba 100644
--- a/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java
+++ b/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java
@@ -17,10 +17,13 @@
 
 package org.apache.jmeter.threads;
 
+import org.apache.jorphan.reflect.JMeterService;
+
 /**
  * Interface notified when number of active threads changes
  * @since 2.10
  */
+@JMeterService
 public interface RemoteThreadsLifeCycleListener {
 
     /**
diff --git a/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsListenerImpl.java b/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsListenerImpl.java
index adb0f593a4..058702827a 100644
--- a/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsListenerImpl.java
+++ b/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsListenerImpl.java
@@ -17,18 +17,16 @@
 
 package org.apache.jmeter.threads;
 
-import java.io.IOException;
-import java.lang.reflect.Modifier;
 import java.rmi.RemoteException;
 import java.rmi.server.UnicastRemoteObject;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Collection;
+import java.util.ServiceLoader;
 
 import org.apache.jmeter.gui.GuiPackage;
 import org.apache.jmeter.rmi.RmiUtils;
 import org.apache.jmeter.testelement.ThreadListener;
 import org.apache.jmeter.util.JMeterUtils;
-import org.apache.jorphan.reflect.ClassFinder;
+import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -39,7 +37,14 @@ import org.slf4j.LoggerFactory;
 public class RemoteThreadsListenerImpl extends UnicastRemoteObject implements
         RemoteThreadsListener, ThreadListener {
     private static final Logger log = LoggerFactory.getLogger(RemoteThreadsListenerImpl.class);
-    private final List<RemoteThreadsLifeCycleListener> listeners = new ArrayList<>();
+
+    private final Collection<RemoteThreadsLifeCycleListener> listeners =
+            JMeterUtils.loadServicesAndScanJars(
+                    RemoteThreadsLifeCycleListener.class,
+                    ServiceLoader.load(RemoteThreadsLifeCycleListener.class),
+                    Thread.currentThread().getContextClassLoader(),
+                    new LogAndIgnoreServiceLoadExceptionHandler(log)
+            );
 
     /**
      *
@@ -56,27 +61,6 @@ public class RemoteThreadsListenerImpl extends UnicastRemoteObject implements
      */
     public RemoteThreadsListenerImpl() throws RemoteException {
         super(DEFAULT_LOCAL_PORT, RmiUtils.createClientSocketFactory(), RmiUtils.createServerSocketFactory());
-        try {
-            List<String> listClasses = ClassFinder.findClassesThatExtend(
-                    JMeterUtils.getSearchPaths(),
-                    new Class[] {RemoteThreadsLifeCycleListener.class });
-            for (String strClassName : listClasses) {
-                try {
-                    log.debug("Loading class: {}", strClassName);
-                    Class<?> commandClass = Class.forName(strClassName);
-                    if (!Modifier.isAbstract(commandClass.getModifiers())) {
-                        log.debug("Instantiating: {}", commandClass);
-                        RemoteThreadsLifeCycleListener listener = (RemoteThreadsLifeCycleListener) commandClass.getDeclaredConstructor().newInstance();
-                        listeners.add(listener);
-                    }
-                } catch (Exception e) {
-                    log.error("Exception registering {} with implementation: {}", RemoteThreadsLifeCycleListener.class,
-                            strClassName, e);
-                }
-            }
-        } catch (IOException e) {
-            log.error("Exception finding implementations of {}", RemoteThreadsLifeCycleListener.class, e);
-        }
     }
 
     private static int addOffset(int port, int offset) {
diff --git a/src/core/src/main/java/org/apache/jmeter/util/JMeterUtils.java b/src/core/src/main/java/org/apache/jmeter/util/JMeterUtils.java
index 3a8408bb2a..de46f1b684 100644
--- a/src/core/src/main/java/org/apache/jmeter/util/JMeterUtils.java
+++ b/src/core/src/main/java/org/apache/jmeter/util/JMeterUtils.java
@@ -27,19 +27,25 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
 import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
 import java.net.InetAddress;
 import java.net.URL;
 import java.net.UnknownHostException;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Enumeration;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.MissingResourceException;
 import java.util.Properties;
 import java.util.ResourceBundle;
+import java.util.ServiceLoader;
+import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.stream.Collectors;
@@ -59,6 +65,7 @@ import org.apache.jmeter.threads.JMeterContextService;
 import org.apache.jorphan.gui.JFactory;
 import org.apache.jorphan.gui.JMeterUIDefaults;
 import org.apache.jorphan.reflect.ClassFinder;
+import org.apache.jorphan.reflect.ServiceLoadExceptionHandler;
 import org.apache.jorphan.test.UnitTestManager;
 import org.apache.jorphan.util.JMeterError;
 import org.apache.jorphan.util.JOrphanUtils;
@@ -316,6 +323,71 @@ public class JMeterUtils implements UnitTestManager {
         getProperties(file);
     }
 
+    /**
+     * Loads services implementing a given interface and scans JMeter search path for the implementations.
+     * This is a transition replacement for {@link ClassFinder}, and JMeter would migrate to {@link ServiceLoader}-only
+     * lookup in the future.
+     * <p>Note: it is not always safe to cache the result as {@code search_paths} property might change over time</p>
+     *
+     * @param service interface that services should extend.
+     * @param serviceLoader ServiceLoader to fetch services.
+     * @param classLoader classLoader to use when searching for classes on the search path.
+     * @param exceptionHandler exception handler to use for services that fail to load.
+     * @return collection of services that load successfully
+     * @param <S> type of service (class or interface)
+     */
+    @API(status = API.Status.DEPRECATED, since = "5.6")
+    public static <S> Collection<S> loadServicesAndScanJars(
+            @SuppressWarnings("BoundedWildcard") Class<S> service,
+            ServiceLoader<S> serviceLoader,
+            ClassLoader classLoader,
+            ServiceLoadExceptionHandler<? super S> exceptionHandler
+    ) {
+        Collection<S> services = ClassFinder.loadServices(service, serviceLoader, exceptionHandler);
+
+        List<String> classesFromJars;
+        try (ClassFinder.Closeable ignored = ClassFinder.skipJarsWithJmeterSkipClassScanningAttribute()) {
+            classesFromJars = findClassesThatExtend(service);
+        } catch (IOException e) {
+            log.warn("Unable to lookup {} with ClassFinder.findClassesThatExtend. " +
+                    "Will use only results from ServiceLoader ({} items found)", service, services.size(), e);
+            return services;
+        }
+
+        if (classesFromJars.isEmpty()) {
+            return services;
+        }
+
+        Set<String> loadedClasses = new HashSet<>((int) (services.size() / 0.75f) + 1);
+        for (S s : services) {
+            loadedClasses.add(s.getClass().getName());
+        }
+
+        List<S> result = new ArrayList<>(services.size() + classesFromJars.size());
+        result.addAll(services);
+        for (String className : classesFromJars) {
+            // Ignore classes that we loaded previously (e.g. with a ServiceLoader)
+            if (!loadedClasses.add(className)) {
+                continue;
+            }
+            try {
+                Class<? extends S> klass = Class.forName(className, false, classLoader)
+                        .asSubclass(service);
+                if (!Modifier.isAbstract(klass.getModifiers())) {
+                    continue;
+                }
+                result.add(klass.getDeclaredConstructor().newInstance());
+            } catch (Throwable e) {
+                if (e instanceof InvocationTargetException) {
+                    //noinspection AssignmentToCatchBlockParameter
+                    e = e.getCause();
+                }
+                exceptionHandler.handle(service, className, e);
+            }
+        }
+        return result;
+    }
+
     /**
      * Convenience method for
      * {@link ClassFinder#findClassesThatExtend(String[], Class[], boolean)}
@@ -325,7 +397,10 @@ public class JMeterUtils implements UnitTestManager {
      * @param superClass - single class to search for
      * @return List of Strings containing discovered class names.
      * @throws IOException when the used {@link ClassFinder} throws one while searching for the class
+     * @deprecated use {@link #loadServicesAndScanJars(Class, ServiceLoader, ClassLoader, ServiceLoadExceptionHandler)} instead
      */
+    @API(status = API.Status.DEPRECATED, since = "5.6")
+    @Deprecated
     public static List<String> findClassesThatExtend(Class<?> superClass)
         throws IOException {
         return ClassFinder.findClassesThatExtend(getSearchPaths(), new Class[]{superClass}, false);
diff --git a/src/core/src/test/java/org/apache/jorphan/test/AllTests.java b/src/core/src/test/java/org/apache/jorphan/test/AllTests.java
index bbbeefa46f..e64fb568de 100644
--- a/src/core/src/test/java/org/apache/jorphan/test/AllTests.java
+++ b/src/core/src/test/java/org/apache/jorphan/test/AllTests.java
@@ -321,8 +321,10 @@ public final class AllTests {
         }
     }
 
+    @SuppressWarnings("deprecation")
     private static List<String> findJMeterJUnitTests(String searchPathString) throws IOException {
         final String[] searchPaths = JOrphanUtils.split(searchPathString, ",");
+        // TODO: do we really need class searching here?
         return ClassFinder.findClasses(searchPaths, new JunitTestFilter());
     }
 
diff --git a/src/dist-check/src/test/java/org/apache/jmeter/junit/JMeterTest.java b/src/dist-check/src/test/java/org/apache/jmeter/junit/JMeterTest.java
index 7054ff33b5..19152c90ab 100644
--- a/src/dist-check/src/test/java/org/apache/jmeter/junit/JMeterTest.java
+++ b/src/dist-check/src/test/java/org/apache/jmeter/junit/JMeterTest.java
@@ -440,6 +440,7 @@ public class JMeterTest extends JMeterTestCaseJUnit implements Describable {
 
     public static Collection<Object> getObjects(Class<?> extendsClass) throws Throwable {
         String exName = extendsClass.getName();
+        @SuppressWarnings("deprecation")
         Iterator<String> classes = ClassFinder
                 .findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { extendsClass }).iterator();
         List<Object> objects = new ArrayList<>();
diff --git a/src/dist-check/src/test/java/org/apache/jmeter/testbeans/gui/PackageTest.java b/src/dist-check/src/test/java/org/apache/jmeter/testbeans/gui/PackageTest.java
index 03e9704f31..f076fb3283 100644
--- a/src/dist-check/src/test/java/org/apache/jmeter/testbeans/gui/PackageTest.java
+++ b/src/dist-check/src/test/java/org/apache/jmeter/testbeans/gui/PackageTest.java
@@ -171,6 +171,7 @@ public final class PackageTest extends JMeterTestCaseJUnit implements Describabl
     public static Test suite() throws Exception {
         TestSuite suite = new TestSuite("Bean Resource Test Suite");
 
+        @SuppressWarnings("deprecation")
         List<String> testBeanClassNames = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { TestBean.class });
 
         boolean errorDetected = false;
diff --git a/src/dist-check/src/test/java/org/apache/jorphan/reflect/TestClassFinder.java b/src/dist-check/src/test/java/org/apache/jorphan/reflect/TestClassFinder.java
index a8a355670f..a035d32142 100644
--- a/src/dist-check/src/test/java/org/apache/jorphan/reflect/TestClassFinder.java
+++ b/src/dist-check/src/test/java/org/apache/jorphan/reflect/TestClassFinder.java
@@ -48,6 +48,7 @@ public class TestClassFinder {
 
     @Test
     public void testFindClassesThatExtendStringArrayClassOfQArray() throws IOException {
+        @SuppressWarnings("deprecation")
         List<String> findClassesThatExtend = ClassFinder.findClassesThatExtend(
                 libDirs,
                 new Class<?>[] { Exception.class });
@@ -56,6 +57,7 @@ public class TestClassFinder {
 
     @Test
     public void testFindClassesThatExtendStringArrayClassOfQArrayTrue() throws Exception {
+        @SuppressWarnings("deprecation")
         List<String> findClassesThatExtend = ClassFinder.findClassesThatExtend(
                 libDirs,
                 new Class<?>[] { Object.class },
@@ -66,6 +68,7 @@ public class TestClassFinder {
 
     @Test
     public void testFindClassesThatExtendStringArrayClassOfQArrayFalse() throws Exception {
+        @SuppressWarnings("deprecation")
         List<String> findClassesThatExtend = ClassFinder.findClassesThatExtend(
                 libDirs,
                 new Class<?>[] { Exception.class },
@@ -77,6 +80,7 @@ public class TestClassFinder {
 
     @Test
     public void testFindClassesThatExtendStringArrayClassOfQArrayBooleanStringString() throws Exception {
+        @SuppressWarnings("deprecation")
         List<String> findClassesThatExtend = ClassFinder.findClassesThatExtend(
                 libDirs,
                 new Class<?>[] { Exception.class },
@@ -90,6 +94,7 @@ public class TestClassFinder {
 
     @Test
     public void testFindClassesThatExtendStringArrayClassOfQArrayBooleanStringStringTrue() throws Exception {
+        @SuppressWarnings("deprecation")
         List<String> annotatedClasses = ClassFinder.findClassesThatExtend(
                 libDirs,
                 new Class<?>[] { java.beans.Transient.class },
@@ -102,7 +107,7 @@ public class TestClassFinder {
 
     @Test
     public void testFindAnnotatedClasses() throws Exception {
-        @SuppressWarnings("unchecked")
+        @SuppressWarnings({"deprecation", "unchecked"})
         List<String> annotatedClasses = ClassFinder.findAnnotatedClasses(
                 libDirs,
                 new Class[] { java.beans.Transient.class});
@@ -111,7 +116,7 @@ public class TestClassFinder {
 
     @Test
     public void testFindAnnotatedInnerClasses() throws Exception {
-        @SuppressWarnings("unchecked")
+        @SuppressWarnings({"deprecation", "unchecked"})
         List<String> annotatedClasses = ClassFinder.findAnnotatedClasses(libDirs,
                 new Class[] { java.lang.Deprecated.class}, true);
         Assert.assertTrue(annotatedClasses.stream().anyMatch(s->s.contains("$")));
@@ -119,12 +124,16 @@ public class TestClassFinder {
 
     @Test
     public void testFindClasses() throws IOException {
-        Assert.assertFalse(ClassFinder.findClasses(libDirs, className -> true).isEmpty());
+        @SuppressWarnings("deprecation")
+        List<String> classes = ClassFinder.findClasses(libDirs, className -> true);
+        Assert.assertFalse(classes.isEmpty());
     }
 
     @Test
     public void testFindClassesNone() throws IOException {
-        Assert.assertTrue(ClassFinder.findClasses(libDirs, className -> false).isEmpty());
+        @SuppressWarnings("deprecation")
+        List<String> classes = ClassFinder.findClasses(libDirs, className -> false);
+        Assert.assertTrue(classes.isEmpty());
     }
 
 }
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/BeanShell.java b/src/functions/src/main/java/org/apache/jmeter/functions/BeanShell.java
index 88d64cd8e6..f72284c877 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/BeanShell.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/BeanShell.java
@@ -32,10 +32,13 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * A function which understands BeanShell
  * @since 1.X
  */
+@AutoService(Function.class)
 public class BeanShell extends AbstractFunction {
 
     private static final Logger log = LoggerFactory.getLogger(BeanShell.class);
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/CSVRead.java b/src/functions/src/main/java/org/apache/jmeter/functions/CSVRead.java
index c9a80a34a5..612a83ec1c 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/CSVRead.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/CSVRead.java
@@ -28,6 +28,8 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * The function represented by this class allows data to be read from CSV files.
  * Syntax is similar to StringFromFile function. The function allows the test to
@@ -53,6 +55,7 @@ import org.slf4j.LoggerFactory;
  * {@code __CSVRead(*ONE,1);}, etc.
  * @since 1.9
  */
+@AutoService(Function.class)
 public class CSVRead extends AbstractFunction {
     private static final Logger log = LoggerFactory.getLogger(CSVRead.class);
 
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/ChangeCase.java b/src/functions/src/main/java/org/apache/jmeter/functions/ChangeCase.java
index deb5c88a05..38e15271a2 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/ChangeCase.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/ChangeCase.java
@@ -31,6 +31,8 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Change Case Function
  *
@@ -45,6 +47,7 @@ import org.slf4j.LoggerFactory;
  * @since 4.0
  *
  */
+@AutoService(Function.class)
 public class ChangeCase extends AbstractFunction {
     private static final Logger LOGGER = LoggerFactory.getLogger(ChangeCase.class);
     private static final List<String> DESC = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/CharFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/CharFunction.java
index 94e5df40c1..482c7ed6e6 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/CharFunction.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/CharFunction.java
@@ -28,10 +28,13 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Function to generate chars from a list of decimal or hex values
  * @since 2.3.3
  */
+@AutoService(Function.class)
 public class CharFunction extends AbstractFunction {
 
     private static final Logger log = LoggerFactory.getLogger(CharFunction.class);
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/DateTimeConvertFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/DateTimeConvertFunction.java
index c382ba1747..47e9a0d612 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/DateTimeConvertFunction.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/DateTimeConvertFunction.java
@@ -31,6 +31,8 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * DateConvert function to change date format
  * Can optionally store it in a variable.
@@ -38,6 +40,7 @@ import org.slf4j.LoggerFactory;
  * @since 4.0
  *
  */
+@AutoService(Function.class)
 public class DateTimeConvertFunction extends AbstractFunction {
     private static final Logger log = LoggerFactory.getLogger(DateTimeConvertFunction.class);
 
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/DigestEncodeFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/DigestEncodeFunction.java
index 3bc2d990f5..36c9f479e8 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/DigestEncodeFunction.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/DigestEncodeFunction.java
@@ -34,6 +34,8 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Digest Encode Function that provides computing of different SHA-XXX, can
  * uppercase the result and store it in a variable.
@@ -44,6 +46,7 @@ import org.slf4j.LoggerFactory;
  *
  * @since 4.0
  */
+@AutoService(Function.class)
 public class DigestEncodeFunction extends AbstractFunction {
 
     private static final Logger log = LoggerFactory.getLogger(DigestEncodeFunction.class);
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/EscapeHtml.java b/src/functions/src/main/java/org/apache/jmeter/functions/EscapeHtml.java
index 4b3d4186c8..8606779ad0 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/EscapeHtml.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/EscapeHtml.java
@@ -27,6 +27,8 @@ import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * <p>Function which escapes the characters in a <code>String</code> using HTML entities.</p>
  *
@@ -46,6 +48,7 @@ import org.apache.jmeter.util.JMeterUtils;
  * @see StringEscapeUtils#escapeHtml4(String) (Commons Lang)
  * @since 2.3.3
  */
+@AutoService(Function.class)
 public class EscapeHtml extends AbstractFunction {
 
     private static final List<String> desc = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/EscapeOroRegexpChars.java b/src/functions/src/main/java/org/apache/jmeter/functions/EscapeOroRegexpChars.java
index 91c8d99641..a3fbf62aa6 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/EscapeOroRegexpChars.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/EscapeOroRegexpChars.java
@@ -30,10 +30,13 @@ import org.apache.oro.text.regex.Perl5Compiler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Escape ORO meta characters
  * @since 2.9
  */
+@AutoService(Function.class)
 public class EscapeOroRegexpChars extends AbstractFunction {
     private static final Logger log = LoggerFactory.getLogger(EscapeOroRegexpChars.class);
 
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/EscapeXml.java b/src/functions/src/main/java/org/apache/jmeter/functions/EscapeXml.java
index 4dce679a76..ac83b05b77 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/EscapeXml.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/EscapeXml.java
@@ -27,6 +27,8 @@ import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * <p>Function which escapes the characters in a <code>String</code> using XML 1.0 entities.</p>
  *
@@ -43,6 +45,7 @@ import org.apache.jmeter.util.JMeterUtils;
  * @see StringEscapeUtils#escapeXml10(String) (Commons Lang)
  * @since 3.2
  */
+@AutoService(Function.class)
 public class EscapeXml extends AbstractFunction {
 
     private static final List<String> desc = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/EvalFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/EvalFunction.java
index 33c90be05f..e5126c7731 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/EvalFunction.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/EvalFunction.java
@@ -26,6 +26,8 @@ import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Function to evaluate a string which may contain variable or function references.
  *
@@ -34,6 +36,7 @@ import org.apache.jmeter.util.JMeterUtils;
  * Returns: the evaluated value
  * @since 2.3.1
  */
+@AutoService(Function.class)
 public class EvalFunction extends AbstractFunction {
 
     private static final List<String> desc = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/EvalVarFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/EvalVarFunction.java
index 4d3521e81e..71fc5bf409 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/EvalVarFunction.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/EvalVarFunction.java
@@ -29,6 +29,8 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Function to evaluate a string which may contain variable or function references.
  *
@@ -37,6 +39,7 @@ import org.slf4j.LoggerFactory;
  * Returns: the evaluated value
  * @since 2.3.1
  */
+@AutoService(Function.class)
 public class EvalVarFunction extends AbstractFunction {
 
     private static final Logger log = LoggerFactory.getLogger(EvalVarFunction.class);
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/FileToString.java b/src/functions/src/main/java/org/apache/jmeter/functions/FileToString.java
index 519fb69919..5f71500b70 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/FileToString.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/FileToString.java
@@ -32,6 +32,8 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * FileToString Function to read a complete file into a String.
  * <p>
@@ -50,6 +52,7 @@ import org.slf4j.LoggerFactory;
  * </ul>
  * @since 2.4
  */
+@AutoService(Function.class)
 public class FileToString extends AbstractFunction {
     private static final Logger log = LoggerFactory.getLogger(FileToString.class);
 
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/FileWrapper.java b/src/functions/src/main/java/org/apache/jmeter/functions/FileWrapper.java
index a26b482643..30710df41f 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/FileWrapper.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/FileWrapper.java
@@ -26,6 +26,7 @@ import java.util.Map;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+
 /**
  * This class wraps the FileRowColContainer for use across multiple threads.
  * <p>
@@ -34,6 +35,7 @@ import org.slf4j.LoggerFactory;
  * together with the current line number.
  *
  */
+//@AutoService(Function.class)
 public final class FileWrapper {
 
     private static final Logger log = LoggerFactory.getLogger(FileWrapper.class);
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Groovy.java b/src/functions/src/main/java/org/apache/jmeter/functions/Groovy.java
index 5e23c7b5a3..23612d0eb2 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/Groovy.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/Groovy.java
@@ -41,11 +41,14 @@ import org.apache.jmeter.util.JSR223TestElement;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * __groovy function
  * Provides a Groovy interpreter
  * @since 3.1
  */
+@AutoService(Function.class)
 public class Groovy extends AbstractFunction {
     private static final Logger log = LoggerFactory.getLogger(Groovy.class);
 
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/IntSum.java b/src/functions/src/main/java/org/apache/jmeter/functions/IntSum.java
index 92019be77c..fb2db081de 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/IntSum.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/IntSum.java
@@ -27,12 +27,15 @@ import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.threads.JMeterVariables;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Provides an intSum function that adds two or more integer values.
  *
  * @see LongSum
  * @since 1.8.1
  */
+@AutoService(Function.class)
 public class IntSum extends AbstractFunction {
     private static final List<String> desc = new ArrayList<>();
 
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/IsPropDefined.java b/src/functions/src/main/java/org/apache/jmeter/functions/IsPropDefined.java
index eb86e50151..b50922d6a2 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/IsPropDefined.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/IsPropDefined.java
@@ -26,11 +26,14 @@ import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Test if a JMeter property is defined
  *
  * @since 4.0
  */
+@AutoService(Function.class)
 public class IsPropDefined extends AbstractFunction {
     private static final List<String> desc = new ArrayList<>();
     private static final String KEY = "__isPropDefined";
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/IsVarDefined.java b/src/functions/src/main/java/org/apache/jmeter/functions/IsVarDefined.java
index edb8515e6a..99a2e9e1b7 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/IsVarDefined.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/IsVarDefined.java
@@ -27,11 +27,14 @@ import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.threads.JMeterVariables;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Test if a JMeter variable is defined
  *
  * @since 4.0
  */
+@AutoService(Function.class)
 public class IsVarDefined extends AbstractFunction {
 
     private static final List<String> desc = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/IterationCounter.java b/src/functions/src/main/java/org/apache/jmeter/functions/IterationCounter.java
index a5c730b905..4742a9c560 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/IterationCounter.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/IterationCounter.java
@@ -29,11 +29,14 @@ import org.apache.jmeter.testelement.ThreadListener;
 import org.apache.jmeter.threads.JMeterVariables;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Counter that can be referenced anywhere in the Thread Group. It can be configured per User (Thread Local)
  * or globally.
  * @since 1.X
  */
+@AutoService(Function.class)
 public class IterationCounter extends AbstractFunction implements ThreadListener {
 
     private static final List<String> desc = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/JavaScript.java b/src/functions/src/main/java/org/apache/jmeter/functions/JavaScript.java
index 00ea98bc6f..bc4729b6a2 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/JavaScript.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/JavaScript.java
@@ -40,10 +40,13 @@ import org.mozilla.javascript.Scriptable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * javaScript function implementation that executes a piece of JavaScript (not Java!) code and returns its value
  * @since 1.9
  */
+@AutoService(Function.class)
 public class JavaScript extends AbstractFunction {
     private static final String NASHORN_ENGINE_NAME = "nashorn"; //$NON-NLS-1$
 
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Jexl2Function.java b/src/functions/src/main/java/org/apache/jmeter/functions/Jexl2Function.java
index 7b6c185b27..1d90bca74c 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/Jexl2Function.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/Jexl2Function.java
@@ -36,11 +36,14 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * A function which understands Commons JEXL2
  * @since 2.6
  */
 // For unit tests, see TestJexlFunction
+@AutoService(Function.class)
 public class Jexl2Function extends AbstractFunction implements ThreadListener {
 
     private static final Logger log = LoggerFactory.getLogger(Jexl2Function.class);
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Jexl3Function.java b/src/functions/src/main/java/org/apache/jmeter/functions/Jexl3Function.java
index ac74281d47..6c37bc92b0 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/Jexl3Function.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/Jexl3Function.java
@@ -37,11 +37,14 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * A function which understands Commons JEXL3
  * @since 3.0
  */
 // For unit tests, see TestJexlFunction
+@AutoService(Function.class)
 public class Jexl3Function extends AbstractFunction implements ThreadListener {
 
     private static final Logger log = LoggerFactory.getLogger(Jexl3Function.class);
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/LogFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/LogFunction.java
index de5b0f9a57..a40d6d668d 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/LogFunction.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/LogFunction.java
@@ -30,6 +30,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.event.Level;
 
+import com.google.auto.service.AutoService;
+
 /**
  * <p>
  * Function to log a message.
@@ -46,6 +48,7 @@ import org.slf4j.event.Level;
  * Returns: - the input string
  * @since 2.2
  */
+@AutoService(Function.class)
 public class LogFunction extends AbstractFunction {
     private static final Logger log = LoggerFactory.getLogger(LogFunction.class);
 
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/LogFunction2.java b/src/functions/src/main/java/org/apache/jmeter/functions/LogFunction2.java
index 9f45af1ae4..23901d8450 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/LogFunction2.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/LogFunction2.java
@@ -28,6 +28,8 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * <p>
  * Function to log a message.
@@ -43,6 +45,7 @@ import org.slf4j.LoggerFactory;
  * Returns: - Empty String (so can be used where return value would be a nuisance)
  * @since 2.2
  */
+@AutoService(Function.class)
 public class LogFunction2 extends AbstractFunction {
     private static final Logger log = LoggerFactory.getLogger(LogFunction2.class);
 
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/LongSum.java b/src/functions/src/main/java/org/apache/jmeter/functions/LongSum.java
index 37a8e8de51..b29a3551d6 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/LongSum.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/LongSum.java
@@ -27,11 +27,14 @@ import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.threads.JMeterVariables;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Provides a longSum function that adds two or more long values.
  * @see IntSum
  * @since 2.3.2
  */
+@AutoService(Function.class)
 public class LongSum extends AbstractFunction {
 
     private static final List<String> desc = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/MachineIP.java b/src/functions/src/main/java/org/apache/jmeter/functions/MachineIP.java
index 82acc7183b..e80a944d74 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/MachineIP.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/MachineIP.java
@@ -19,10 +19,13 @@ package org.apache.jmeter.functions;
 
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Return Machine IP
  * @since 2.6
  */
+@AutoService(Function.class)
 public class MachineIP extends AbstractHostIPName {
 
     private static final String KEY = "__machineIP"; //$NON-NLS-1$
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/MachineName.java b/src/functions/src/main/java/org/apache/jmeter/functions/MachineName.java
index 50898adff7..8218b8e41f 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/MachineName.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/MachineName.java
@@ -19,10 +19,13 @@ package org.apache.jmeter.functions;
 
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Return Machine Host
  * @since 1.X
  */
+@AutoService(Function.class)
 public class MachineName extends AbstractHostIPName {
 
     private static final String KEY = "__machineName"; //$NON-NLS-1$
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Property.java b/src/functions/src/main/java/org/apache/jmeter/functions/Property.java
index b2168a307f..db4be23411 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/Property.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/Property.java
@@ -27,6 +27,8 @@ import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.threads.JMeterVariables;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Function to get a JMeter property, and optionally store it
  *
@@ -41,6 +43,7 @@ import org.apache.jmeter.util.JMeterUtils;
  * - the property name itself
  * @since 2.0
  */
+@AutoService(Function.class)
 public class Property extends AbstractFunction {
 
     private static final List<String> desc = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Property2.java b/src/functions/src/main/java/org/apache/jmeter/functions/Property2.java
index 701dd0fd46..0694219ba6 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/Property2.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/Property2.java
@@ -26,6 +26,8 @@ import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Function to get a JMeter property, or a default. Does not offer the option to
  * store the value, as it is just as easy to refetch it. This is a
@@ -46,6 +48,7 @@ import org.apache.jmeter.util.JMeterUtils;
  * not present - "1" (suitable for use in ThreadGroup GUI)
  * @since 2.0
  */
+@AutoService(Function.class)
 public class Property2 extends AbstractFunction {
 
     private static final List<String> desc = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Random.java b/src/functions/src/main/java/org/apache/jmeter/functions/Random.java
index 20921a187d..bfffeb7c7a 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/Random.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/Random.java
@@ -28,11 +28,14 @@ import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.threads.JMeterVariables;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Provides a Random function which returns a random long integer between a min
  * (first argument) and a max (second argument).
  * @since 1.9
  */
+@AutoService(Function.class)
 public class Random extends AbstractFunction {
 
     private static final List<String> desc = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/RandomDate.java b/src/functions/src/main/java/org/apache/jmeter/functions/RandomDate.java
index 83243d76f4..d28e815546 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/RandomDate.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/RandomDate.java
@@ -41,6 +41,7 @@ import org.slf4j.LoggerFactory;
 
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
+import com.google.auto.service.AutoService;
 
 /**
  * RandomDate Function generates a date in a specific range
@@ -59,6 +60,7 @@ import com.github.benmanes.caffeine.cache.Caffeine;
  *
  * @since 3.3
  */
+@AutoService(Function.class)
 public class RandomDate extends AbstractFunction {
 
     private static final Logger log = LoggerFactory.getLogger(RandomDate.class);
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/RandomFromMultipleVars.java b/src/functions/src/main/java/org/apache/jmeter/functions/RandomFromMultipleVars.java
index 7a22beca1e..897fd48f11 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/RandomFromMultipleVars.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/RandomFromMultipleVars.java
@@ -31,6 +31,8 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Provides a RandomFromMultipleVars function which returns a random element from a multi valued extracted variable.
  * Those kind of variables are extracted by:
@@ -41,6 +43,7 @@ import org.slf4j.LoggerFactory;
  *
  * @since 3.1
  */
+@AutoService(Function.class)
 public class RandomFromMultipleVars extends AbstractFunction {
     private static final Logger log = LoggerFactory.getLogger(RandomFromMultipleVars.class);
 
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/RandomString.java b/src/functions/src/main/java/org/apache/jmeter/functions/RandomString.java
index 1dd0048392..e0528d1c77 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/RandomString.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/RandomString.java
@@ -31,11 +31,14 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Provides a RandomString function which returns a random String of length (first argument)
  * using characters (second argument)
  * @since 2.6
  */
+@AutoService(Function.class)
 public class RandomString extends AbstractFunction {
     private static final Logger log = LoggerFactory.getLogger(RandomString.class);
 
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/RegexFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/RegexFunction.java
index a3c50e2b4e..8ea9e82019 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/RegexFunction.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/RegexFunction.java
@@ -39,6 +39,8 @@ import org.apache.oro.text.regex.Perl5Compiler;
 import org.apache.oro.text.regex.Util;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+
+import com.google.auto.service.AutoService;
 /**
  * Implements regular expression parsing of sample results and variables
  * @since 1.X
@@ -46,6 +48,7 @@ import org.slf4j.LoggerFactory;
 
 // @see TestRegexFunction for unit tests
 
+@AutoService(Function.class)
 public class RegexFunction extends AbstractFunction {
     private static final Logger log = LoggerFactory.getLogger(RegexFunction.class);
 
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/SamplerName.java b/src/functions/src/main/java/org/apache/jmeter/functions/SamplerName.java
index eedb707b96..fa624db65f 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/SamplerName.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/SamplerName.java
@@ -27,10 +27,13 @@ import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.threads.JMeterVariables;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Function to return the name of the current sampler.
  * @since 2.5
  */
+@AutoService(Function.class)
 public class SamplerName extends AbstractFunction {
 
     private static final String KEY = "__samplerName"; //$NON-NLS-1$
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/SetProperty.java b/src/functions/src/main/java/org/apache/jmeter/functions/SetProperty.java
index 7811c5686c..5e8bce06cf 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/SetProperty.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/SetProperty.java
@@ -27,6 +27,8 @@ import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Function to set a JMeter property
  *
@@ -40,6 +42,7 @@ import org.apache.jmeter.util.JMeterUtils;
  * Returns: nothing or original value if the 3rd parameter is true
  * @since 2.1
  */
+@AutoService(Function.class)
 public class SetProperty extends AbstractFunction {
 
     private static final List<String> desc = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/SplitFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/SplitFunction.java
index 17e8bb1012..825f0d8a62 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/SplitFunction.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/SplitFunction.java
@@ -31,6 +31,8 @@ import org.apache.jorphan.util.JOrphanUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 // @see org.apache.jmeter.functions.PackageTest for unit tests
 
 /**
@@ -53,6 +55,7 @@ import org.slf4j.LoggerFactory;
  * </ul>
  * @since 2.0.2
  */
+@AutoService(Function.class)
 public class SplitFunction extends AbstractFunction {
     private static final Logger log = LoggerFactory.getLogger(SplitFunction.class);
 
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/StringFromFile.java b/src/functions/src/main/java/org/apache/jmeter/functions/StringFromFile.java
index fc2207feee..5e7081e1b0 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/StringFromFile.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/StringFromFile.java
@@ -37,6 +37,8 @@ import org.apache.jorphan.util.JMeterStopThreadException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * <p>StringFromFile Function to read a String from a text file.</p>
  *
@@ -70,6 +72,7 @@ import org.slf4j.LoggerFactory;
  * Because function instances are shared, it does not make sense to use the thread number as part of the file name.
  * @since 1.9
  */
+@AutoService(Function.class)
 public class StringFromFile extends AbstractFunction implements TestStateListener {
     private static final Logger log = LoggerFactory.getLogger(StringFromFile.class);
 
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/StringToFile.java b/src/functions/src/main/java/org/apache/jmeter/functions/StringToFile.java
index 25d644e8ee..41a363976b 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/StringToFile.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/StringToFile.java
@@ -41,6 +41,8 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * StringToFile Function to write a String to a file
  *
@@ -55,6 +57,7 @@ import org.slf4j.LoggerFactory;
  *
  * @since 5.2
  */
+@AutoService(Function.class)
 public class StringToFile extends AbstractFunction {
     private static final Logger log = LoggerFactory.getLogger(StringToFile.class);
     private static final List<String> desc = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/TestPlanName.java b/src/functions/src/main/java/org/apache/jmeter/functions/TestPlanName.java
index c0ab9e8a94..ba298545ac 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/TestPlanName.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/TestPlanName.java
@@ -26,10 +26,13 @@ import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.services.FileServer;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Returns Test Plan name
  * @since 2.6
  */
+@AutoService(Function.class)
 public class TestPlanName extends AbstractFunction {
 
     private static final List<String> desc = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/ThreadGroupName.java b/src/functions/src/main/java/org/apache/jmeter/functions/ThreadGroupName.java
index 08a4386708..36c6b38f46 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/ThreadGroupName.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/ThreadGroupName.java
@@ -26,11 +26,14 @@ import org.apache.jmeter.threads.AbstractThreadGroup;
 import org.apache.jmeter.threads.JMeterContext;
 import org.apache.jmeter.threads.JMeterContextService;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Returns Thread Group Name
  *
  * @since 5.0
  */
+@AutoService(Function.class)
 public class ThreadGroupName extends AbstractFunctionByKey {
     private static final String KEY = "__threadGroupName"; //$NON-NLS-1$
 
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/ThreadNumber.java b/src/functions/src/main/java/org/apache/jmeter/functions/ThreadNumber.java
index 0c353c32e6..12d3221e0b 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/ThreadNumber.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/ThreadNumber.java
@@ -25,10 +25,13 @@ import org.apache.jmeter.engine.util.CompoundVariable;
 import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.samplers.Sampler;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Function to return the current thread number.
  * @since 1.X
  */
+@AutoService(Function.class)
 public class ThreadNumber extends AbstractFunction {
 
     private static final String KEY = "__threadNum"; //$NON-NLS-1$
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/TimeFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/TimeFunction.java
index 65e6c01a9d..1dd087b7ed 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/TimeFunction.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/TimeFunction.java
@@ -35,12 +35,15 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 // See org.apache.jmeter.functions.TestTimeFunction for unit tests
 
 /**
  * __time() function - returns the current time in milliseconds
  * @since 2.2
  */
+@AutoService(Function.class)
 public class TimeFunction extends AbstractFunction {
 
     private static final String KEY = "__time"; // $NON-NLS-1$
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/TimeShift.java b/src/functions/src/main/java/org/apache/jmeter/functions/TimeShift.java
index 131d360dae..2b011401b8 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/TimeShift.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/TimeShift.java
@@ -43,6 +43,7 @@ import org.slf4j.LoggerFactory;
 
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
+import com.google.auto.service.AutoService;
 
 /**
  * timeShifting Function permit to shift a date
@@ -65,6 +66,7 @@ import com.github.benmanes.caffeine.cache.Caffeine;
  *
  * @since 3.3
  */
+@AutoService(Function.class)
 public class TimeShift extends AbstractFunction {
     private static final Logger log = LoggerFactory.getLogger(TimeShift.class);
 
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/UnEscape.java b/src/functions/src/main/java/org/apache/jmeter/functions/UnEscape.java
index 910c4bb48f..933af1421e 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/UnEscape.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/UnEscape.java
@@ -28,6 +28,8 @@ import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Function to unescape any Java literals found in the String.
  * For example, it will turn a sequence of '\' and 'n' into a newline character,
@@ -36,6 +38,7 @@ import org.apache.jmeter.util.JMeterUtils;
  * @see StringEscapeUtils#unescapeJava(String)
  * @since 2.3.3
  */
+@AutoService(Function.class)
 public class UnEscape extends AbstractFunction {
 
     private static final List<String> desc = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/UnEscapeHtml.java b/src/functions/src/main/java/org/apache/jmeter/functions/UnEscapeHtml.java
index 97a8bafa9c..85f57bbe73 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/UnEscapeHtml.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/UnEscapeHtml.java
@@ -28,6 +28,8 @@ import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Function to unescape a string containing entity escapes
  * to a string containing the actual Unicode characters corresponding to the escapes.
@@ -42,6 +44,7 @@ import org.apache.jmeter.util.JMeterUtils;
  * @see StringEscapeUtils#unescapeHtml4(String)
  * @since 2.3.3
  */
+@AutoService(Function.class)
 public class UnEscapeHtml extends AbstractFunction {
 
     private static final List<String> desc = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/UrlDecode.java b/src/functions/src/main/java/org/apache/jmeter/functions/UrlDecode.java
index a31f1aa2a4..9890ec650c 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/UrlDecode.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/UrlDecode.java
@@ -30,11 +30,14 @@ import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Function to decode a application/x-www-form-urlencoded string.
  *
  * @since 2.10
  */
+@AutoService(Function.class)
 public class UrlDecode extends AbstractFunction {
 
     private static final String CHARSET_ENCODING = StandardCharsets.UTF_8.name();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/UrlEncode.java b/src/functions/src/main/java/org/apache/jmeter/functions/UrlEncode.java
index 410ac89a58..e890c4383c 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/UrlEncode.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/UrlEncode.java
@@ -30,11 +30,14 @@ import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Function to encode a string to a application/x-www-form-urlencoded string.
  *
  * @since 2.10
  */
+@AutoService(Function.class)
 public class UrlEncode extends AbstractFunction {
 
     private static final String CHARSET_ENCODING = StandardCharsets.UTF_8.name();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Uuid.java b/src/functions/src/main/java/org/apache/jmeter/functions/Uuid.java
index 16461968cc..b4b2ee370f 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/Uuid.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/Uuid.java
@@ -26,6 +26,8 @@ import org.apache.jmeter.engine.util.CompoundVariable;
 import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.samplers.Sampler;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Function to create a UUID
  *
@@ -36,6 +38,7 @@ import org.apache.jmeter.samplers.Sampler;
  * - A pseudo random UUID 4
  * @since 2.9
  */
+@AutoService(Function.class)
 public class Uuid extends AbstractFunction {
 
     private static final List<String> desc = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Variable.java b/src/functions/src/main/java/org/apache/jmeter/functions/Variable.java
index 18ee52db41..ce83531819 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/Variable.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/Variable.java
@@ -27,6 +27,8 @@ import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.samplers.Sampler;
 import org.apache.jmeter.util.JMeterUtils;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Function to get a JMeter Variable
  *
@@ -39,6 +41,7 @@ import org.apache.jmeter.util.JMeterUtils;
  * - the default value if set, and if not the variable name itself
  * @since 2.3RC3
  */
+@AutoService(Function.class)
 public class Variable extends AbstractFunction {
 
     private static final List<String> desc = new ArrayList<>();
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/XPath.java b/src/functions/src/main/java/org/apache/jmeter/functions/XPath.java
index 1200ccc55a..a2fe3adf68 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/XPath.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/XPath.java
@@ -29,6 +29,8 @@ import org.apache.jmeter.util.JMeterUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 // @see org.apache.jmeter.functions.PackageTest for unit tests
 
 /**
@@ -45,6 +47,7 @@ import org.slf4j.LoggerFactory;
  * is opened and used for all threads.
  * @since 2.0.3
  */
+@AutoService(Function.class)
 public class XPath extends AbstractFunction {
     private static final Logger log = LoggerFactory.getLogger(XPath.class);
 
diff --git a/src/functions/src/test/kotlin/org/apache/jmeter/functions/FunctionServicesTest.kt b/src/functions/src/test/kotlin/org/apache/jmeter/functions/FunctionServicesTest.kt
new file mode 100644
index 0000000000..32ad1d2913
--- /dev/null
+++ b/src/functions/src/test/kotlin/org/apache/jmeter/functions/FunctionServicesTest.kt
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jmeter.functions
+
+import org.apache.jmeter.util.JMeterUtils
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+import java.util.ServiceLoader
+
+class FunctionServicesTest {
+    @Test
+    fun `__counter loads`() {
+        val functions = JMeterUtils.loadServicesAndScanJars(
+            Function::class.java,
+            ServiceLoader.load(Function::class.java),
+            Thread.currentThread().contextClassLoader
+        ) { service, className, throwable ->
+            throw IllegalStateException(
+                "Failed to load $service implementations, implementation: $className",
+                throwable
+            )
+        }.map { it.referenceKey }
+
+        Assertions.assertTrue("__counter" in functions) {
+            "__counter function should be discoverable with ServiceLoader.load(Function), all found functions are $functions"
+        }
+    }
+}
diff --git a/src/jorphan/src/main/java/org/apache/jorphan/reflect/ClassFinder.java b/src/jorphan/src/main/java/org/apache/jorphan/reflect/ClassFinder.java
index 7d04e1374e..22d08caed9 100644
--- a/src/jorphan/src/main/java/org/apache/jorphan/reflect/ClassFinder.java
+++ b/src/jorphan/src/main/java/org/apache/jorphan/reflect/ClassFinder.java
@@ -26,18 +26,24 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.jar.JarFile;
 import java.util.stream.Stream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
+import org.apiguardian.api.API;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * This class finds classes that extend one of a set of parent classes
+ * This class finds classes that extend one of a set of parent classes.
  */
 public final class ClassFinder {
     private static final Logger log = LoggerFactory.getLogger(ClassFinder.class);
@@ -46,10 +52,99 @@ public final class ClassFinder {
     private static final String DOT_CLASS = ".class"; // $NON-NLS-1$
     private static final int DOT_CLASS_LEN = DOT_CLASS.length();
 
+    private static final ThreadLocal<Boolean> SKIP_JARS_WITH_JMETER_SKIP_ATTRIBUTE = new ThreadLocal<>();
+
+    public static final String JMETER_SKIP_CLASS_SCANNING_ATTRIBUTE = "JMeter-Skip-Class-Scanning";
+
+    @API(status = API.Status.EXPERIMENTAL, since = "5.6")
+    public interface Closeable extends AutoCloseable {
+        @Override
+        void close();
+    }
+
     // static only
     private ClassFinder() {
     }
 
+    @API(status = API.Status.EXPERIMENTAL, since = "5.6")
+    public static boolean getSkipJarsWithJmeterSkipClassScanningAttribute() {
+        return Objects.equals(SKIP_JARS_WITH_JMETER_SKIP_ATTRIBUTE.get(), Boolean.TRUE);
+    }
+
+    /**
+     * Configures if {@link ClassFinder} should skip jar files that have {@code JMeter-Skip-Class-Scanning: true}
+     * manifest attribute.
+     * JMeter will skip such jars when it uses both {@link java.util.ServiceLoader} and {@link ClassFinder}.
+     * However, {@link ClassFinder} was public, so it was possible that custom plugins could use it, and they should
+     * be able to find the implementations even if they are in jars with {@code JMeter-Skip-Class-Scanning: true}.
+     * <p>
+     * Sample usage:
+     * <pre>
+     * List&lt;String&gt; classNames;
+     * try (ClassFinder.Closeable ignored = ClassFinder.skipJarsWithJmeterSkipClassScanningAttribute()) {
+     *   // findClassesThatExtend will not skip jars with JMeter-Skip-Class-Scanning: true manifest attribute
+     *   classNames = ClassFinder.findClassesThatExtend(...);
+     * </pre>
+     *
+     * @return closeable that will reset "skip jar files with manifest entry" flag when closed. Use it in try-with-resources
+     */
+    @API(status = API.Status.INTERNAL, since = "5.6")
+    public static Closeable skipJarsWithJmeterSkipClassScanningAttribute() {
+        SKIP_JARS_WITH_JMETER_SKIP_ATTRIBUTE.set(true);
+        return SKIP_JARS_WITH_JMETER_SKIP_ATTRIBUTE::remove;
+    }
+
+    /**
+     * Loads services implementing a given interface.
+     * This is an intended replacement for {@code findClassesThatExtend}.
+     *
+     * @param service interface that services should extend.
+     * @param serviceLoader ServiceLoader to fetch services.
+     * @param exceptionHandler exception handler to use for services that fail to load.
+     * @return collection of services that load successfully
+     * @param <S> type of service (class or interface)
+     */
+    public static <S> Collection<S> loadServices(
+            @SuppressWarnings("BoundedWildcard") Class<S> service,
+            ServiceLoader<S> serviceLoader,
+            ServiceLoadExceptionHandler<? super S> exceptionHandler
+    ) {
+        List<S> result = new ArrayList<>();
+        @SuppressWarnings("ForEachIterable")
+        Iterator<S> it = serviceLoader.iterator();
+        while (it.hasNext()) {
+            try {
+                // This can't be for-each loop because we need to catch exceptions from next()
+                result.add(it.next());
+            } catch (ServiceConfigurationError e) {
+                // Java does not expose class name of the problematic class in question, so we extract it
+                // from the message
+                String message = e.getMessage();
+                String className = "";
+                if (message.startsWith(service.getName())) {
+                    if (message.endsWith(" Unable to get public no-arg constructor")) {
+                        className = message.substring(
+                                service.getName().length() + ": ".length(),
+                                message.length() - " Unable to get public no-arg constructor".length()
+                        );
+                    } else if (message.endsWith(" not a subtype")) {
+                        className = message.substring(
+                                service.getName().length() + ": ".length(),
+                                message.length() - " not a subtype".length()
+                        );
+                    } else if (message.endsWith(" could not be instantiated")) {
+                        className = message.substring(
+                                service.getName().length() + ": ".length() + "Provider ".length(),
+                                message.length() - " could not be instantiated".length()
+                        );
+                    }
+                }
+                exceptionHandler.handle(service, className, e);
+            }
+        }
+        return Collections.unmodifiableCollection(result);
+    }
+
     /**
      * Filter updates by only storing classes
      * that extend one of the parent classes
@@ -176,7 +271,9 @@ public final class ClassFinder {
      * @param superClasses required parent class(es)
      * @return List of Strings containing discovered class names.
      * @throws IOException when scanning the classes fails
+     * @deprecated use {@link #loadServices(Class, ServiceLoader, ServiceLoadExceptionHandler)} or {@code JMeterUtils#loadServicesAndScanJars}
      */
+    @Deprecated
     public static List<String> findClassesThatExtend(String[] paths, Class<?>[] superClasses)
             throws IOException {
         return findClassesThatExtend(paths, superClasses, false);
@@ -206,7 +303,9 @@ public final class ClassFinder {
      * @param innerClasses   should we include inner classes?
      * @return List containing discovered classes
      * @throws IOException when scanning for classes fails
+     * @deprecated use {@link #loadServices(Class, ServiceLoader, ServiceLoadExceptionHandler)} or {@code JMeterUtils#loadServicesAndScanJars}
      */
+    @Deprecated
     public static List<String> findClassesThatExtend(String[] strPathsOrJars,
             final Class<?>[] superClasses, final boolean innerClasses)
             throws IOException  {
@@ -223,7 +322,10 @@ public final class ClassFinder {
      * @param notContains    classname should not contain this string
      * @return List containing discovered classes
      * @throws IOException when scanning classes fails
+     * @deprecated use {@link #loadServices(Class, ServiceLoader, ServiceLoadExceptionHandler)} or {@code JMeterUtils#loadServicesAndScanJars}
      */
+    @API(status = API.Status.DEPRECATED, since = "5.6")
+    @Deprecated
     public static List<String> findClassesThatExtend(String[] strPathsOrJars,
             final Class<?>[] superClasses, final boolean innerClasses,
             String contains, String notContains)
@@ -239,7 +341,10 @@ public final class ClassFinder {
      * @param innerClasses   should we include inner classes?
      * @return List containing discovered classes
      * @throws IOException when scanning classes fails
+     * @deprecated use {@link #loadServices(Class, ServiceLoader, ServiceLoadExceptionHandler)} or {@code JMeterUtils#loadServicesAndScanJars}
      */
+    @API(status = API.Status.DEPRECATED, since = "5.6")
+    @Deprecated
     public static List<String> findAnnotatedClasses(String[] strPathsOrJars,
             final Class<? extends Annotation>[] annotations, final boolean innerClasses)
             throws IOException  {
@@ -254,7 +359,10 @@ public final class ClassFinder {
      * @param annotations    required annotations
      * @return List containing discovered classes
      * @throws IOException when scanning classes fails
+     * @deprecated use {@link #loadServices(Class, ServiceLoader, ServiceLoadExceptionHandler)} or {@code JMeterUtils#loadServicesAndScanJars}
      */
+    @API(status = API.Status.DEPRECATED, since = "5.6")
+    @Deprecated
     public static List<String> findAnnotatedClasses(String[] strPathsOrJars,
             final Class<? extends Annotation>[] annotations)
             throws IOException  {
@@ -272,7 +380,10 @@ public final class ClassFinder {
      * @param annotations       true if classnames are annotations
      * @return List containing discovered classes
      * @throws IOException when scanning classes fails
+     * @deprecated use {@link #loadServices(Class, ServiceLoader, ServiceLoadExceptionHandler)} or {@code JMeterUtils#loadServicesAndScanJars}
      */
+    @API(status = API.Status.DEPRECATED, since = "5.6")
+    @Deprecated
     public static List<String> findClassesThatExtend(String[] searchPathsOrJars,
                 final Class<?>[] classNames, final boolean innerClasses,
                 String contains, String notContains, boolean annotations)
@@ -307,7 +418,10 @@ public final class ClassFinder {
      *                          conform to
      * @return list of all classes in the jars, that conform to {@code filter}
      * @throws IOException when reading the jar files fails
+     * @deprecated use {@link #loadServices(Class, ServiceLoader, ServiceLoadExceptionHandler)} or {@code JMeterUtils#loadServicesAndScanJars}
      */
+    @API(status = API.Status.DEPRECATED, since = "5.6")
+    @Deprecated
     public static List<String> findClasses(String[] searchPathsOrJars, ClassFilter filter) throws IOException {
         if (log.isDebugEnabled()) {
             log.debug("findClasses with searchPathsOrJars : {} and classFilter : {}",
@@ -352,10 +466,22 @@ public final class ClassFinder {
     }
 
 
-    private static void findClassesInOnePath(File file, Set<String> listClasses, ClassFilter filter) throws IOException {
+    private static void findClassesInOnePath(File file, Set<String> listClasses, ClassFilter filter) {
         if (file.isDirectory()) {
             findClassesInPathsDir(file.getAbsolutePath(), file, listClasses, filter);
         } else if (file.exists()) {
+            if (getSkipJarsWithJmeterSkipClassScanningAttribute() && file.getName().endsWith(DOT_JAR)) {
+                // Ignore jars with JMeter-Skip-Class-Scanning attribute
+                try (JarFile jar = new JarFile(file)) {
+                    String value = jar.getManifest().getMainAttributes().getValue(JMETER_SKIP_CLASS_SCANNING_ATTRIBUTE);
+                    if (Boolean.parseBoolean(value)) {
+                        log.info("Jar {} is skipped for scanning since it has {}={} attribute", file, JMETER_SKIP_CLASS_SCANNING_ATTRIBUTE, value);
+                        return;
+                    }
+                } catch (IOException e) {
+                    log.warn("Can not open the jar {}, message: {}", file.getAbsolutePath(), e.getLocalizedMessage(), e);
+                }
+            }
             try (ZipFile zipFile = new ZipFile(file);
                  Stream<? extends ZipEntry> entries = zipFile.stream()) {
                 entries.filter(entry -> entry.getName().endsWith(DOT_CLASS))
@@ -371,7 +497,7 @@ public final class ClassFinder {
     }
 
 
-    private static void findClassesInPathsDir(String strPathElement, File dir, Set<String> listClasses, ClassFilter filter) throws IOException {
+    private static void findClassesInPathsDir(String strPathElement, File dir, Set<String> listClasses, ClassFilter filter) {
         File[] list = dir.listFiles();
         if (list == null) {
             log.warn("{} is not a folder", dir.getAbsolutePath());
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/MachineName.java b/src/jorphan/src/main/java/org/apache/jorphan/reflect/CollectServiceLoadExceptionHandler.java
similarity index 54%
copy from src/functions/src/main/java/org/apache/jmeter/functions/MachineName.java
copy to src/jorphan/src/main/java/org/apache/jorphan/reflect/CollectServiceLoadExceptionHandler.java
index 50898adff7..41f80d4df8 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/MachineName.java
+++ b/src/jorphan/src/main/java/org/apache/jorphan/reflect/CollectServiceLoadExceptionHandler.java
@@ -15,29 +15,26 @@
  * limitations under the License.
  */
 
-package org.apache.jmeter.functions;
+package org.apache.jorphan.reflect;
 
-import org.apache.jmeter.util.JMeterUtils;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 
 /**
- * Return Machine Host
- * @since 1.X
+ * Collects all the failures in a collection.
+ * @param <S> the service type
  */
-public class MachineName extends AbstractHostIPName {
-
-    private static final String KEY = "__machineName"; //$NON-NLS-1$
-
-    public MachineName() {
-    }
+public class CollectServiceLoadExceptionHandler<S> implements ServiceLoadExceptionHandler<S> {
+    private final List<ServiceLoadFailure<S>> failures = new ArrayList<>();
 
     @Override
-    protected String compute() {
-        return JMeterUtils.getLocalHostName();
+    public void handle(Class<? extends S> service, String className, Throwable throwable) {
+        failures.add(new ServiceLoadFailure<>(service, className, throwable));
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public String getReferenceKey() {
-        return KEY;
+    public Collection<ServiceLoadFailure<S>> toCollection() {
+        return Collections.unmodifiableCollection(failures);
     }
 }
diff --git a/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts b/src/jorphan/src/main/java/org/apache/jorphan/reflect/IgnoreServiceLoadExceptionHandler.java
similarity index 74%
copy from build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts
copy to src/jorphan/src/main/java/org/apache/jorphan/reflect/IgnoreServiceLoadExceptionHandler.java
index 534f894dfc..3ee2cf88b4 100644
--- a/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts
+++ b/src/jorphan/src/main/java/org/apache/jorphan/reflect/IgnoreServiceLoadExceptionHandler.java
@@ -15,7 +15,13 @@
  * limitations under the License.
  */
 
-plugins {
-    id("build-logic.java")
-    id("java-library")
+package org.apache.jorphan.reflect;
+
+/**
+ * Ignores all failures.
+ */
+public class IgnoreServiceLoadExceptionHandler implements ServiceLoadExceptionHandler<Object> {
+    @Override
+    public void handle(Class<?> service, String className, Throwable throwable) {
+    }
 }
diff --git a/src/jorphan/src/main/java/org/apache/jorphan/reflect/JMeterService.java b/src/jorphan/src/main/java/org/apache/jorphan/reflect/JMeterService.java
new file mode 100644
index 0000000000..c641256f03
--- /dev/null
+++ b/src/jorphan/src/main/java/org/apache/jorphan/reflect/JMeterService.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jorphan.reflect;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.apiguardian.api.API;
+
+/**
+ * This is a marker annotation that describes JMeter will try using {@link java.util.ServiceLoader} to find the implementations.
+ * If the plugin exposes the service via service loader (META-INF/services) then it will improve JMeter startup.
+ * <p>Note: JMeter will still try scanning the classes in the jars for backward compatibility reasons,
+ * so if you expose services, then consider adding {@code JMeter-Skip-Class-Scanning: true} manifest attribute
+ * to your jar file. JMeter will skip scanning class files in such jars</p>
+ * @since 5.6
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@API(status = API.Status.EXPERIMENTAL, since = "5.6")
+public @interface JMeterService {
+}
diff --git a/src/jorphan/src/main/java/org/apache/jorphan/reflect/LogAndIgnoreServiceLoadExceptionHandler.java b/src/jorphan/src/main/java/org/apache/jorphan/reflect/LogAndIgnoreServiceLoadExceptionHandler.java
new file mode 100644
index 0000000000..5959bb54cc
--- /dev/null
+++ b/src/jorphan/src/main/java/org/apache/jorphan/reflect/LogAndIgnoreServiceLoadExceptionHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jorphan.reflect;
+
+import org.slf4j.Logger;
+
+/**
+ * Logs all the failures to agiven {@link Logger} and ignores them.
+ */
+public class LogAndIgnoreServiceLoadExceptionHandler implements ServiceLoadExceptionHandler<Object> {
+    private final Logger log;
+
+    public LogAndIgnoreServiceLoadExceptionHandler(Logger log) {
+        this.log = log;
+    }
+
+    @Override
+    public void handle(Class<?> service, String className, Throwable throwable) {
+        if (throwable instanceof NoClassDefFoundError) {
+            if (throwable.getMessage().contains("javafx")) {
+                log.error(
+                        "Exception registering implementation: [{}] of interface: [{}], a dependency used by the plugin class is missing. " +
+                                "Add JavaFX to your Java installation if you want to use renderer: {}",
+                        className, service, className, throwable
+                );
+            } else {
+                log.error(
+                        "Exception registering implementation: [{}] of interface: [{}], a dependency used by the plugin class is missing",
+                        className, service, throwable
+                );
+            }
+        } else {
+            log.error(
+                    "Exception registering implementation: [{}] of interface: [{}], a jar is probably missing",
+                    className, service, throwable
+            );
+        }
+    }
+}
diff --git a/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java b/src/jorphan/src/main/java/org/apache/jorphan/reflect/RethrowServiceLoadExceptionHandler.java
similarity index 65%
copy from src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java
copy to src/jorphan/src/main/java/org/apache/jorphan/reflect/RethrowServiceLoadExceptionHandler.java
index f2c8d99012..91400f3c8e 100644
--- a/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java
+++ b/src/jorphan/src/main/java/org/apache/jorphan/reflect/RethrowServiceLoadExceptionHandler.java
@@ -15,23 +15,14 @@
  * limitations under the License.
  */
 
-package org.apache.jmeter.threads;
+package org.apache.jorphan.reflect;
 
 /**
- * Interface notified when number of active threads changes
- * @since 2.10
+ * Rethrows service loading failures as {@link IllegalStateException}.
  */
-public interface RemoteThreadsLifeCycleListener {
-
-    /**
-     *
-     * @param numberOfThreads number of active threads
-     */
-    void threadNumberIncreased(int numberOfThreads);
-
-    /**
-     *
-     * @param numberOfThreads number of active threads
-     */
-    void threadNumberDecreased(int numberOfThreads);
+public class RethrowServiceLoadExceptionHandler implements ServiceLoadExceptionHandler<Object> {
+    @Override
+    public void handle(Class<?> service, String className, Throwable throwable) {
+        throw new IllegalStateException("Can't load class " + className + " for instantiating service " + service, throwable);
+    }
 }
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/MachineIP.java b/src/jorphan/src/main/java/org/apache/jorphan/reflect/ServiceLoadExceptionHandler.java
similarity index 57%
copy from src/functions/src/main/java/org/apache/jmeter/functions/MachineIP.java
copy to src/jorphan/src/main/java/org/apache/jorphan/reflect/ServiceLoadExceptionHandler.java
index 82acc7183b..816c2931db 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/MachineIP.java
+++ b/src/jorphan/src/main/java/org/apache/jorphan/reflect/ServiceLoadExceptionHandler.java
@@ -15,29 +15,22 @@
  * limitations under the License.
  */
 
-package org.apache.jmeter.functions;
+package org.apache.jorphan.reflect;
 
-import org.apache.jmeter.util.JMeterUtils;
+import org.apiguardian.api.API;
 
 /**
- * Return Machine IP
- * @since 2.6
+ * Service loading might fail (e.g. due to a missing dependency).
+ * This handler enables client code factor the failure handing.
+ *
+ * @param <S> type of the service
+ * @since 5.6
+ * @see IgnoreServiceLoadExceptionHandler
+ * @see LogAndIgnoreServiceLoadExceptionHandler
+ * @see RethrowServiceLoadExceptionHandler
  */
-public class MachineIP extends AbstractHostIPName {
-
-    private static final String KEY = "__machineIP"; //$NON-NLS-1$
-
-    public MachineIP() {
-    }
-
-    @Override
-    protected String compute() {
-        return JMeterUtils.getLocalHostIP();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public String getReferenceKey() {
-        return KEY;
-    }
+@FunctionalInterface
+@API(status = API.Status.EXPERIMENTAL, since = "5.6")
+public interface ServiceLoadExceptionHandler<S> {
+    void handle(Class<? extends S> service, String className, Throwable throwable);
 }
diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java b/src/jorphan/src/main/java/org/apache/jorphan/reflect/ServiceLoadFailure.java
similarity index 51%
copy from src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java
copy to src/jorphan/src/main/java/org/apache/jorphan/reflect/ServiceLoadFailure.java
index d92f150120..825f39aeb6 100644
--- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java
+++ b/src/jorphan/src/main/java/org/apache/jorphan/reflect/ServiceLoadFailure.java
@@ -15,25 +15,37 @@
  * limitations under the License.
  */
 
-package org.apache.jmeter.visualizers;
+package org.apache.jorphan.reflect;
 
-import org.apache.jmeter.samplers.SampleResult;
-import org.apache.jmeter.util.JMeterUtils;
+public class ServiceLoadFailure<S> {
+    private final Class<? extends S> service;
+    private final String className;
+    private final Throwable throwable;
 
-public class RenderAsHTMLWithEmbedded extends RenderAsHTML
-    implements ResultRenderer {
+    public ServiceLoadFailure(Class<? extends S> service, String className, Throwable throwable) {
+        this.service = service;
+        this.className = className;
+        this.throwable = throwable;
+    }
 
-    /** {@inheritDoc} */
-    @Override
-    protected void showRenderedResponse(String response, SampleResult res) {
-        // enable embedded html resources
-        showRenderedResponse(response, res, true);
+    public Class<? extends S> getService() {
+        return service;
+    }
+
+    public String getClassName() {
+        return className;
+    }
+
+    public Throwable getThrowable() {
+        return throwable;
     }
 
-    /** {@inheritDoc} */
     @Override
     public String toString() {
-        return JMeterUtils.getResString("view_results_render_html_embedded"); // $NON-NLS-1$
+        return "ServiceLoadFailure{" +
+                "service=" + service +
+                ", className='" + className + '\'' +
+                ", throwable=" + throwable +
+                '}';
     }
-
 }
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java
index d9388b3050..966c3502af 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java
@@ -63,6 +63,7 @@ import org.apache.jmeter.gui.GuiPackage;
 import org.apache.jmeter.gui.action.AbstractAction;
 import org.apache.jmeter.gui.action.ActionNames;
 import org.apache.jmeter.gui.action.ActionRouter;
+import org.apache.jmeter.gui.action.Command;
 import org.apache.jmeter.gui.plugin.MenuCreator;
 import org.apache.jmeter.gui.tree.JMeterTreeModel;
 import org.apache.jmeter.gui.tree.JMeterTreeNode;
@@ -110,12 +111,18 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.xml.sax.SAXException;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Opens a popup where user can enter a cURL command line and create a test plan
  * from it
  *
  * @since 5.1
  */
+@AutoService({
+        Command.class,
+        MenuCreator.class
+})
 public class ParseCurlCommandAction extends AbstractAction implements MenuCreator, ActionListener { // NOSONAR
     private static final Logger LOGGER = LoggerFactory.getLogger(ParseCurlCommandAction.class);
     private static final String ACCEPT_ENCODING = "Accept-Encoding";
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/DefaultSamplerCreator.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/DefaultSamplerCreator.java
index 051389da24..27b9894be6 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/DefaultSamplerCreator.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/DefaultSamplerCreator.java
@@ -57,10 +57,12 @@ import org.xml.sax.helpers.DefaultHandler;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.auto.service.AutoService;
 
 /**
  * Default implementation that handles classical HTTP textual + Multipart requests
  */
+@AutoService(SamplerCreator.class)
 public class DefaultSamplerCreator extends AbstractSamplerCreator {
     private static final Logger log = LoggerFactory.getLogger(DefaultSamplerCreator.class);
 
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/SamplerCreator.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/SamplerCreator.java
index 81bc9d99f5..54768d353d 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/SamplerCreator.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/SamplerCreator.java
@@ -23,10 +23,12 @@ import java.util.Map;
 import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase;
 import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.testelement.TestElement;
+import org.apache.jorphan.reflect.JMeterService;
 
 /**
  * Factory of sampler
  */
+@JMeterService
 public interface SamplerCreator {
 
     /**
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/SamplerCreatorFactory.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/SamplerCreatorFactory.java
index 3f3549cfb3..679db1dbe8 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/SamplerCreatorFactory.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/SamplerCreatorFactory.java
@@ -17,14 +17,12 @@
 
 package org.apache.jmeter.protocol.http.proxy;
 
-import java.io.IOException;
-import java.lang.reflect.Modifier;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
+import java.util.ServiceLoader;
 
 import org.apache.jmeter.util.JMeterUtils;
-import org.apache.jorphan.reflect.ClassFinder;
+import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -59,41 +57,27 @@ public class SamplerCreatorFactory {
      * Initialize factory from classpath
      */
     private void init() { // WARNING: called from ctor so must not be overridden (i.e. must be private or final)
-        try {
-            List<String> listClasses = ClassFinder.findClassesThatExtend(
-                    JMeterUtils.getSearchPaths(),
-                    new Class[] {SamplerCreator.class });
-            for (String strClassName : listClasses) {
-                try {
-                    if(log.isDebugEnabled()) {
-                        log.debug("Loading class: {}", strClassName);
+        for (SamplerCreator creator : JMeterUtils.loadServicesAndScanJars(
+                SamplerCreator.class,
+                ServiceLoader.load(SamplerCreator.class),
+                Thread.currentThread().getContextClassLoader(),
+                new LogAndIgnoreServiceLoadExceptionHandler(log)
+        )) {
+            try {
+                String[] contentTypes = creator.getManagedContentTypes();
+                for (String contentType : contentTypes) {
+                    log.debug("Registering samplerCreator {} for content type:{}",
+                            creator.getClass().getName(), contentType);
+                    SamplerCreator oldSamplerCreator = samplerCreatorMap.put(contentType, creator);
+                    if (oldSamplerCreator != null) {
+                        log.warn("A sampler creator was already registered for:{}, class:{}, it will be replaced",
+                                contentType, oldSamplerCreator.getClass());
                     }
-                    Class<?> commandClass = Class.forName(strClassName);
-                    if (!Modifier.isAbstract(commandClass.getModifiers())) {
-                        if(log.isDebugEnabled()) {
-                            log.debug("Instantiating: {}", commandClass.getName());
-                        }
-                        SamplerCreator creator = (SamplerCreator) commandClass.getDeclaredConstructor().newInstance();
-                        String[] contentTypes = creator.getManagedContentTypes();
-                        for (String contentType : contentTypes) {
-                            if(log.isDebugEnabled()) {
-                                log.debug("Registering samplerCreator {} for content type:{}",
-                                        commandClass.getName(), contentType);
-                            }
-                            SamplerCreator oldSamplerCreator = samplerCreatorMap.put(contentType, creator);
-                            if(oldSamplerCreator!=null) {
-                                log.warn("A sampler creator was already registered for:{}, class:{}, it will be replaced",
-                                        contentType, oldSamplerCreator.getClass());
-                            }
-                        }
-                    }
-                } catch (Exception e) {
-                    log.error("Exception registering {} with implementation:{}",
-                            SamplerCreator.class.getName(),strClassName, e);
                 }
+            } catch (Exception e) {
+                log.error("Exception registering {} with implementation:{}",
+                        SamplerCreator.class.getName(), creator.getClass(), e);
             }
-        } catch (IOException e) {
-            log.error("Exception finding implementations of {}", SamplerCreator.class, e);
         }
     }
 
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerBeanInfo.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerBeanInfo.java
index 45aa4e1b91..2495527214 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerBeanInfo.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerBeanInfo.java
@@ -18,90 +18,92 @@
 package org.apache.jmeter.protocol.http.sampler;
 
 import java.beans.PropertyDescriptor;
-import java.io.IOException;
-import java.util.Collections;
 import java.util.List;
+import java.util.ServiceLoader;
+import java.util.stream.Collectors;
 
 import org.apache.jmeter.protocol.http.util.accesslog.Filter;
 import org.apache.jmeter.protocol.http.util.accesslog.LogParser;
 import org.apache.jmeter.testbeans.BeanInfoSupport;
 import org.apache.jmeter.testbeans.gui.FileEditor;
 import org.apache.jmeter.util.JMeterUtils;
-import org.apache.jorphan.reflect.ClassFinder;
+import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public class AccessLogSamplerBeanInfo extends BeanInfoSupport {
     private static final Logger log = LoggerFactory.getLogger(AccessLogSamplerBeanInfo.class);
-    private static final List<String> LOG_PARSER_CLASSES = logParsers();
-
-    private static List<String> logParsers() {
-        try {
-            return ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { LogParser.class });
-        } catch (IOException e) {
-            log.warn("Could not find log parsers.", e);
-            return Collections.emptyList();
-        }
-    }
+    private static final List<String> LOG_PARSER_CLASSES =
+            JMeterUtils.loadServicesAndScanJars(
+                    LogParser.class,
+                    ServiceLoader.load(LogParser.class),
+                    Thread.currentThread().getContextClassLoader(),
+                    new LogAndIgnoreServiceLoadExceptionHandler(log)
+            ).stream()
+                    .map(s -> s.getClass().getName())
+                    .sorted()
+                    .collect(Collectors.toList());
 
     public AccessLogSamplerBeanInfo() {
         super(AccessLogSampler.class);
         log.debug("Entered access log sampler bean info");
-        try {
-            createPropertyGroup("defaults",  // $NON-NLS-1$
-                    new String[] { "protocol", "domain", "portString", "imageParsing" });// $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ $NON-NLS-4$
-
-            createPropertyGroup("plugins",  // $NON-NLS-1$
-                    new String[] { "parserClassName", "filterClassName" }); // $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$
-
-            createPropertyGroup("accesslogfile",  // $NON-NLS-1$
-                    new String[] { "logFile" }); // $NON-NLS-1$
-
-            PropertyDescriptor p;
-
-            p = property("parserClassName");
-            p.setValue(NOT_UNDEFINED, Boolean.TRUE);
-            p.setValue(DEFAULT, AccessLogSampler.DEFAULT_CLASS);
-            p.setValue(NOT_OTHER, Boolean.TRUE);
-            p.setValue(NOT_EXPRESSION, Boolean.TRUE);
-
-            log.debug("found parsers: {}", LOG_PARSER_CLASSES);
-            p.setValue(TAGS, LOG_PARSER_CLASSES.toArray(new String[LOG_PARSER_CLASSES.size()]));
-
-            p = property("filterClassName"); // $NON-NLS-1$
-            p.setValue(NOT_UNDEFINED, Boolean.FALSE);
-            p.setValue(DEFAULT, ""); // $NON-NLS-1$
-            p.setValue(NOT_EXPRESSION, Boolean.TRUE);
-            List<String> classes = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(),
-                    new Class[] { Filter.class }, false);
-            p.setValue(TAGS, classes.toArray(new String[classes.size()]));
-
-            p = property("logFile"); // $NON-NLS-1$
-            p.setValue(NOT_UNDEFINED, Boolean.TRUE);
-            p.setValue(DEFAULT, "");
-            p.setPropertyEditorClass(FileEditor.class);
-
-            p = property("domain"); // $NON-NLS-1$
-            p.setValue(NOT_UNDEFINED, Boolean.TRUE);
-            p.setValue(DEFAULT, "");
-
-            p = property("protocol"); // $NON-NLS-1$
-            p.setValue(NOT_UNDEFINED, Boolean.TRUE);
-            p.setValue(DEFAULT, "http"); // $NON-NLS-1$
-            p.setValue(DEFAULT_NOT_SAVED, Boolean.TRUE);
-
-            p = property("portString"); // $NON-NLS-1$
-            p.setValue(NOT_UNDEFINED, Boolean.TRUE);
-            p.setValue(DEFAULT, ""); // $NON-NLS-1$
-
-            p = property("imageParsing"); // $NON-NLS-1$
-            p.setValue(NOT_UNDEFINED, Boolean.TRUE);
-            p.setValue(DEFAULT, Boolean.FALSE);
-            p.setValue(NOT_OTHER, Boolean.TRUE);
-        } catch (IOException e) {
-            log.warn("couldn't find classes and set up properties", e);
-            throw new RuntimeException("Could not find classes with class finder", e);
-        }
+        createPropertyGroup("defaults",  // $NON-NLS-1$
+                new String[] { "protocol", "domain", "portString", "imageParsing" });// $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ $NON-NLS-4$
+
+        createPropertyGroup("plugins",  // $NON-NLS-1$
+                new String[] { "parserClassName", "filterClassName" }); // $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$
+
+        createPropertyGroup("accesslogfile",  // $NON-NLS-1$
+                new String[] { "logFile" }); // $NON-NLS-1$
+
+        PropertyDescriptor p;
+
+        p = property("parserClassName");
+        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
+        p.setValue(DEFAULT, AccessLogSampler.DEFAULT_CLASS);
+        p.setValue(NOT_OTHER, Boolean.TRUE);
+        p.setValue(NOT_EXPRESSION, Boolean.TRUE);
+
+        log.debug("found parsers: {}", LOG_PARSER_CLASSES);
+        p.setValue(TAGS, LOG_PARSER_CLASSES.toArray(new String[0]));
+
+        p = property("filterClassName"); // $NON-NLS-1$
+        p.setValue(NOT_UNDEFINED, Boolean.FALSE);
+        p.setValue(DEFAULT, ""); // $NON-NLS-1$
+        p.setValue(NOT_EXPRESSION, Boolean.TRUE);
+        String[] classes = JMeterUtils.loadServicesAndScanJars(
+                        Filter.class,
+                        ServiceLoader.load(Filter.class),
+                        Thread.currentThread().getContextClassLoader(),
+                        new LogAndIgnoreServiceLoadExceptionHandler(log)
+                ).stream()
+                .map(s -> s.getClass().getName())
+                .sorted()
+                .toArray(String[]::new);
+        p.setValue(TAGS, classes);
+
+        p = property("logFile"); // $NON-NLS-1$
+        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
+        p.setValue(DEFAULT, "");
+        p.setPropertyEditorClass(FileEditor.class);
+
+        p = property("domain"); // $NON-NLS-1$
+        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
+        p.setValue(DEFAULT, "");
+
+        p = property("protocol"); // $NON-NLS-1$
+        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
+        p.setValue(DEFAULT, "http"); // $NON-NLS-1$
+        p.setValue(DEFAULT_NOT_SAVED, Boolean.TRUE);
+
+        p = property("portString"); // $NON-NLS-1$
+        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
+        p.setValue(DEFAULT, ""); // $NON-NLS-1$
+
+        p = property("imageParsing"); // $NON-NLS-1$
+        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
+        p.setValue(DEFAULT, Boolean.FALSE);
+        p.setValue(NOT_OTHER, Boolean.TRUE);
         log.debug("Got to end of access log sampler bean info init");
     }
 
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/Filter.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/Filter.java
index 182c361b1f..f78d6bf170 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/Filter.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/Filter.java
@@ -18,6 +18,7 @@
 package org.apache.jmeter.protocol.http.util.accesslog;
 
 import org.apache.jmeter.testelement.TestElement;
+import org.apache.jorphan.reflect.JMeterService;
 
 /**
  * Description:<br>
@@ -35,6 +36,7 @@ import org.apache.jmeter.testelement.TestElement;
  *
  */
 
+@JMeterService
 public interface Filter {
 
     /**
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/LogFilter.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/LogFilter.java
index 698e03953a..ce9487fb12 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/LogFilter.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/LogFilter.java
@@ -29,6 +29,8 @@ import org.apache.oro.text.regex.Perl5Compiler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Description:<br>
  * <br>
@@ -69,6 +71,7 @@ import org.slf4j.LoggerFactory;
  * that should be replaced.
  */
 @SuppressWarnings("InconsistentCapitalization")
+@AutoService(Filter.class)
 public class LogFilter implements Filter, Serializable {
 
     private static final long serialVersionUID = 241L;
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/LogParser.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/LogParser.java
index d08809c9e5..c29fa43b35 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/LogParser.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/LogParser.java
@@ -18,6 +18,7 @@
 package org.apache.jmeter.protocol.http.util.accesslog;
 
 import org.apache.jmeter.testelement.TestElement;
+import org.apache.jorphan.reflect.JMeterService;
 
 /**
  * Description:<br>
@@ -34,6 +35,7 @@ import org.apache.jmeter.testelement.TestElement;
  *
  */
 
+@JMeterService
 public interface LogParser {
 
     /**
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/OrderPreservingLogParser.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/OrderPreservingLogParser.java
index 581a21acdc..3f1f5dbc7b 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/OrderPreservingLogParser.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/OrderPreservingLogParser.java
@@ -19,6 +19,9 @@ package org.apache.jmeter.protocol.http.util.accesslog;
 
 import org.apache.jmeter.testelement.TestElement;
 
+import com.google.auto.service.AutoService;
+
+@AutoService(LogParser.class)
 public class OrderPreservingLogParser extends SharedTCLogParser {
 
     public OrderPreservingLogParser() {
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/SessionFilter.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/SessionFilter.java
index e573aec218..6ffef587c3 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/SessionFilter.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/SessionFilter.java
@@ -37,9 +37,12 @@ import org.apache.oro.text.regex.Perl5Matcher;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Provides Session Filtering for the AccessLog Sampler.
  */
+@AutoService(Filter.class)
 public class SessionFilter implements Filter, Serializable, TestCloneable,ThreadListener {
     private static final java.util.regex.Pattern IP_PATTERN = java.util.regex.Pattern.compile("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
     private static final long serialVersionUID = 233L;
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/SharedTCLogParser.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/SharedTCLogParser.java
index 9e429e03e1..d070216956 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/SharedTCLogParser.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/SharedTCLogParser.java
@@ -23,6 +23,9 @@ import org.apache.jmeter.services.FileServer;
 import org.apache.jmeter.testelement.TestCloneable;
 import org.apache.jmeter.testelement.TestElement;
 
+import com.google.auto.service.AutoService;
+
+@AutoService(LogParser.class)
 public class SharedTCLogParser extends TCLogParser implements TestCloneable {
 
     public SharedTCLogParser() {
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/TCLogParser.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/TCLogParser.java
index ce79dd0bab..72a45b25d2 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/TCLogParser.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/TCLogParser.java
@@ -36,6 +36,8 @@ import org.apache.jmeter.testelement.TestElement;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Description:<br>
  * <br>
@@ -72,6 +74,7 @@ import org.slf4j.LoggerFactory;
  */
 
 @SuppressWarnings("InconsistentCapitalization")
+@AutoService(LogParser.class)
 public class TCLogParser implements LogParser {
     protected static final Logger log = LoggerFactory.getLogger(TCLogParser.class);
 
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/visualizers/RequestViewHTTP.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/visualizers/RequestViewHTTP.java
index 0a0790ee02..5eb198062e 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/visualizers/RequestViewHTTP.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/visualizers/RequestViewHTTP.java
@@ -56,9 +56,12 @@ import org.apache.jorphan.reflect.Functor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * Specializer panel to view a HTTP request parsed
  */
+@AutoService(RequestView.class)
 public class RequestViewHTTP implements RequestView {
 
     private static final Logger log = LoggerFactory.getLogger(RequestViewHTTP.class);
diff --git a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/config/gui/JavaConfigGui.java b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/config/gui/JavaConfigGui.java
index 53b128ce78..ea25256a6e 100644
--- a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/config/gui/JavaConfigGui.java
+++ b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/config/gui/JavaConfigGui.java
@@ -18,12 +18,13 @@
 package org.apache.jmeter.protocol.java.config.gui;
 
 import java.awt.BorderLayout;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.ServiceLoader;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import javax.swing.ImageIcon;
 import javax.swing.JLabel;
@@ -47,7 +48,7 @@ import org.apache.jmeter.testelement.property.JMeterProperty;
 import org.apache.jmeter.util.JMeterUtils;
 import org.apache.jorphan.gui.JFactory;
 import org.apache.jorphan.gui.JLabeledChoice;
-import org.apache.jorphan.reflect.ClassFinder;
+import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -136,21 +137,15 @@ public class JavaConfigGui extends AbstractConfigGui implements ChangeListener {
      * @return a panel containing the relevant components
      */
     private JPanel createClassnamePanel() {
-        List<String> possibleClasses = new ArrayList<>();
-
-        try {
-            // Find all the classes which implement the JavaSamplerClient
-            // interface.
-            possibleClasses = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(),
-                    new Class[] { JavaSamplerClient.class });
-
-            // Remove the JavaConfig class from the list since it only
-            // implements the interface for error conditions.
-
-            possibleClasses.remove(JavaSampler.class.getName() + "$ErrorSamplerClient");
-        } catch (Exception e) {
-            log.debug("Exception getting interfaces.", e);
-        }
+        List<String> possibleClasses = JMeterUtils.loadServicesAndScanJars(
+                JavaSamplerClient.class,
+                ServiceLoader.load(JavaSamplerClient.class),
+                Thread.currentThread().getContextClassLoader(),
+                new LogAndIgnoreServiceLoadExceptionHandler(log)
+        ).stream()
+                .map(s -> s.getClass().getName())
+                .sorted()
+                .collect(Collectors.toList());
 
         classNameLabeledChoice = new JLabeledChoice(
                 JMeterUtils.getResString("protocol_java_classname"),
diff --git a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/sampler/JavaSamplerClient.java b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/sampler/JavaSamplerClient.java
index ea884511a0..fdfff46b6f 100644
--- a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/sampler/JavaSamplerClient.java
+++ b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/sampler/JavaSamplerClient.java
@@ -19,6 +19,7 @@ package org.apache.jmeter.protocol.java.sampler;
 
 import org.apache.jmeter.config.Arguments;
 import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jorphan.reflect.JMeterService;
 
 /**
  * This interface defines the interactions between the JavaSampler and external
@@ -59,6 +60,7 @@ import org.apache.jmeter.samplers.SampleResult;
  * how to implement this interface.
  *
  */
+@JMeterService
 public interface JavaSamplerClient {
     /**
      * Do any initialization required by this client. It is generally
diff --git a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/test/JavaTest.java b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/test/JavaTest.java
index 83ee435a79..5efcb1fe18 100644
--- a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/test/JavaTest.java
+++ b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/test/JavaTest.java
@@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit;
 
 import org.apache.jmeter.config.Arguments;
 import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;
+import org.apache.jmeter.protocol.java.sampler.JavaSamplerClient;
 import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
 import org.apache.jmeter.samplers.Interruptible;
 import org.apache.jmeter.samplers.SampleResult;
@@ -30,6 +31,8 @@ import org.apache.jmeter.testelement.TestElement;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * The <code>JavaTest</code> class is a simple sampler which is intended for
  * use when developing test plans. The sampler generates results internally, so
@@ -66,7 +69,7 @@ import org.slf4j.LoggerFactory;
  * Note: this class was derived from {@link SleepTest}.
  *
  */
-
+@AutoService(JavaSamplerClient.class)
 public class JavaTest extends AbstractJavaSamplerClient implements Serializable, Interruptible {
 
     private static final Logger LOG = LoggerFactory.getLogger(JavaTest.class);
diff --git a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/test/SleepTest.java b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/test/SleepTest.java
index 25ddbbdadf..3a8ee15084 100644
--- a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/test/SleepTest.java
+++ b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/test/SleepTest.java
@@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit;
 
 import org.apache.jmeter.config.Arguments;
 import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;
+import org.apache.jmeter.protocol.java.sampler.JavaSamplerClient;
 import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
 import org.apache.jmeter.samplers.Interruptible;
 import org.apache.jmeter.samplers.SampleResult;
@@ -30,6 +31,8 @@ import org.apache.jmeter.testelement.TestElement;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.auto.service.AutoService;
+
 /**
  * The <code>SleepTest</code> class is a simple example class for a JMeter
  * Java protocol client. The class implements the <code>JavaSamplerClient</code>
@@ -47,6 +50,7 @@ import org.slf4j.LoggerFactory;
  * time.
  *
  */
+@AutoService(JavaSamplerClient.class)
 public class SleepTest extends AbstractJavaSamplerClient implements Serializable, Interruptible {
 
     private static final Logger LOG = LoggerFactory.getLogger(SleepTest.class);
diff --git a/src/protocol/junit/src/main/java/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java b/src/protocol/junit/src/main/java/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java
index da8a0540ed..b2dc6e8175 100644
--- a/src/protocol/junit/src/main/java/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java
+++ b/src/protocol/junit/src/main/java/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java
@@ -171,10 +171,14 @@ implements ChangeListener, ActionListener, ItemListener
             if(initialize) {
                 synchronized (IS_INITILIAZED) {
                     if(IS_INITILIAZED.compareAndSet(false, true)) {
-                        annotatedTestClasses = ClassFinder.findAnnotatedClasses(SPATHS,
-                            new Class[] {Test.class}, false);
-                        junitTestClasses = ClassFinder.findClassesThatExtend(SPATHS,
-                             new Class[] { TestCase.class });
+                        @SuppressWarnings("deprecation")
+                        List<String> annotatedClasses = ClassFinder.findAnnotatedClasses(SPATHS,
+                                new Class[]{Test.class}, false);
+                        annotatedTestClasses = annotatedClasses;
+                        @SuppressWarnings("deprecation")
+                        List<String> junitClasses = ClassFinder.findClassesThatExtend(SPATHS,
+                                new Class[]{TestCase.class});
+                        junitTestClasses = junitClasses;
                     }
                     if (junit4.isSelected()){
                         classList = annotatedTestClasses;
diff --git a/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts b/src/test-services/build.gradle.kts
similarity index 86%
copy from build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts
copy to src/test-services/build.gradle.kts
index 534f894dfc..9efcceed31 100644
--- a/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts
+++ b/src/test-services/build.gradle.kts
@@ -16,6 +16,10 @@
  */
 
 plugins {
-    id("build-logic.java")
-    id("java-library")
+    id("build-logic.jvm-library")
+}
+
+dependencies {
+    implementation(projects.src.jorphan)
+    implementation(projects.src.core)
 }
diff --git a/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/AbstractService.kt
similarity index 76%
copy from build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts
copy to src/test-services/src/main/kotlin/org/apache/jmeter/util/services/AbstractService.kt
index 534f894dfc..b3cc906b97 100644
--- a/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts
+++ b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/AbstractService.kt
@@ -15,7 +15,11 @@
  * limitations under the License.
  */
 
-plugins {
-    id("build-logic.java")
-    id("java-library")
-}
+package org.apache.jmeter.util.services
+
+import com.google.auto.service.AutoService
+
+@AutoService(AbstractServiceInterface::class)
+public abstract class AbstractService : AbstractServiceInterface
+
+public interface AbstractServiceInterface
diff --git a/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceNotImplementingInterface.kt
similarity index 78%
copy from build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts
copy to src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceNotImplementingInterface.kt
index 534f894dfc..e70ff8f82c 100644
--- a/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts
+++ b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceNotImplementingInterface.kt
@@ -15,7 +15,11 @@
  * limitations under the License.
  */
 
-plugins {
-    id("build-logic.java")
-    id("java-library")
-}
+package org.apache.jmeter.util.services
+
+import com.google.auto.service.AutoService
+
+@AutoService(NotImplementedInterface::class)
+public class ServiceNotImplementingInterface
+
+public interface NotImplementedInterface
diff --git a/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceThrowingException.kt
similarity index 64%
copy from src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java
copy to src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceThrowingException.kt
index f2c8d99012..7c16a54ae6 100644
--- a/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java
+++ b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceThrowingException.kt
@@ -15,23 +15,17 @@
  * limitations under the License.
  */
 
-package org.apache.jmeter.threads;
+package org.apache.jmeter.util.services
 
-/**
- * Interface notified when number of active threads changes
- * @since 2.10
- */
-public interface RemoteThreadsLifeCycleListener {
-
-    /**
-     *
-     * @param numberOfThreads number of active threads
-     */
-    void threadNumberIncreased(int numberOfThreads);
+import com.google.auto.service.AutoService
 
-    /**
-     *
-     * @param numberOfThreads number of active threads
-     */
-    void threadNumberDecreased(int numberOfThreads);
+@AutoService(ServiceThrowingExceptionInterface::class)
+public class ServiceThrowingException : ServiceThrowingExceptionInterface {
+    init {
+        throw ServiceFailureException("exception in constructor for test purposes")
+    }
 }
+
+public class ServiceFailureException(message: String) : Throwable(message)
+
+public interface ServiceThrowingExceptionInterface
diff --git a/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceWithPrivateConstructor.kt
similarity index 72%
copy from build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts
copy to src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceWithPrivateConstructor.kt
index 534f894dfc..2710984ea7 100644
--- a/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts
+++ b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceWithPrivateConstructor.kt
@@ -15,7 +15,11 @@
  * limitations under the License.
  */
 
-plugins {
-    id("build-logic.java")
-    id("java-library")
-}
+package org.apache.jmeter.util.services
+
+import com.google.auto.service.AutoService
+
+@AutoService(ServiceWithPrivateConstructorInterface::class)
+public class ServiceWithPrivateConstructor private constructor() : ServiceWithPrivateConstructorInterface
+
+public interface ServiceWithPrivateConstructorInterface
diff --git a/build-logic/jvm/src/main/kotlin/build-logic.jvm-library.gradle.kts b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/WorkableService.kt
similarity index 69%
copy from build-logic/jvm/src/main/kotlin/build-logic.jvm-library.gradle.kts
copy to src/test-services/src/main/kotlin/org/apache/jmeter/util/services/WorkableService.kt
index cabe6e0282..af134427d2 100644
--- a/build-logic/jvm/src/main/kotlin/build-logic.jvm-library.gradle.kts
+++ b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/WorkableService.kt
@@ -15,15 +15,17 @@
  * limitations under the License.
  */
 
-plugins {
-    id("build-logic.java")
-    id("java-library")
-}
+package org.apache.jmeter.util.services
+
+import com.google.auto.service.AutoService
+
+@AutoService(WorkableServiceInterface::class)
+public class WorkableService : WorkableServiceInterface {
+    override val name: String = "test service"
 
-if (file("src/main/groovy").isDirectory || file("src/test/groovy").isDirectory) {
-    apply(plugin = "build-logic.groovy")
+    override fun toString(): String = name
 }
 
-if (file("src/main/kotlin").isDirectory || file("src/test/kotlin").isDirectory) {
-    apply(plugin = "build-logic.kotlin")
+public interface WorkableServiceInterface {
+    public val name: String
 }
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/MachineIP.java b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/loadServices.kt
similarity index 62%
copy from src/functions/src/main/java/org/apache/jmeter/functions/MachineIP.java
copy to src/test-services/src/main/kotlin/org/apache/jmeter/util/services/loadServices.kt
index 82acc7183b..81102d7205 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/MachineIP.java
+++ b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/loadServices.kt
@@ -15,29 +15,18 @@
  * limitations under the License.
  */
 
-package org.apache.jmeter.functions;
+package org.apache.jmeter.util.services
 
-import org.apache.jmeter.util.JMeterUtils;
+import org.apache.jmeter.util.JMeterUtils
+import org.apache.jorphan.reflect.ServiceLoadExceptionHandler
+import java.util.ServiceLoader
 
-/**
- * Return Machine IP
- * @since 2.6
- */
-public class MachineIP extends AbstractHostIPName {
-
-    private static final String KEY = "__machineIP"; //$NON-NLS-1$
-
-    public MachineIP() {
-    }
-
-    @Override
-    protected String compute() {
-        return JMeterUtils.getLocalHostIP();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public String getReferenceKey() {
-        return KEY;
-    }
-}
+public inline fun <reified S : Any> loadServices(
+    exceptionHandler: ServiceLoadExceptionHandler<S>
+): Collection<S> =
+    JMeterUtils.loadServicesAndScanJars(
+        S::class.java,
+        ServiceLoader.load(S::class.java),
+        Thread.currentThread().contextClassLoader,
+        exceptionHandler
+    )
diff --git a/src/test-services/src/test/kotlin/ServiceLoaderTest.kt b/src/test-services/src/test/kotlin/ServiceLoaderTest.kt
new file mode 100644
index 0000000000..3c4d1d1957
--- /dev/null
+++ b/src/test-services/src/test/kotlin/ServiceLoaderTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.jmeter.util.services.AbstractServiceInterface
+import org.apache.jmeter.util.services.NotImplementedInterface
+import org.apache.jmeter.util.services.ServiceThrowingExceptionInterface
+import org.apache.jmeter.util.services.ServiceWithPrivateConstructorInterface
+import org.apache.jmeter.util.services.WorkableServiceInterface
+import org.apache.jmeter.util.services.loadServices
+import org.apache.jorphan.reflect.CollectServiceLoadExceptionHandler
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertAll
+
+class ServiceLoaderTest {
+    @Test
+    fun `service without public constructor`() {
+        assertServiceLoad<ServiceWithPrivateConstructorInterface>(
+            "[]",
+            "[service: org.apache.jmeter.util.services.ServiceWithPrivateConstructorInterface, className: org.apache.jmeter.util.services.ServiceWithPrivateConstructor, exceptionClass: java.util.ServiceConfigurationError, causeClass: java.lang.NoSuchMethodException]"
+        )
+    }
+
+    @Test
+    fun `service not implementing interface`() {
+        assertServiceLoad<NotImplementedInterface>(
+            "[]",
+            "[service: org.apache.jmeter.util.services.NotImplementedInterface, className: org.apache.jmeter.util.services.ServiceNotImplementingInterface, exceptionClass: java.util.ServiceConfigurationError, causeClass: null]"
+        )
+    }
+
+    @Test
+    fun `service failing in constructor`() {
+        assertServiceLoad<ServiceThrowingExceptionInterface>(
+            "[]",
+            "[service: org.apache.jmeter.util.services.ServiceThrowingExceptionInterface, className: org.apache.jmeter.util.services.ServiceThrowingException, exceptionClass: java.util.ServiceConfigurationError, causeClass: org.apache.jmeter.util.services.ServiceFailureException]"
+        )
+    }
+
+    @Test
+    fun `abstract service`() {
+        assertServiceLoad<AbstractServiceInterface>(
+            "[]",
+            "[service: org.apache.jmeter.util.services.AbstractServiceInterface, className: org.apache.jmeter.util.services.AbstractService, exceptionClass: java.util.ServiceConfigurationError, causeClass: java.lang.InstantiationException]"
+        )
+    }
+
+    @Test
+    fun `service loads`() {
+        assertServiceLoad<WorkableServiceInterface>(
+            "[test service]",
+            "[]"
+        )
+    }
+
+    private inline fun <reified S : Any> assertServiceLoad(
+        successMessage: String,
+        failureMessage: String,
+    ) {
+        val failures = CollectServiceLoadExceptionHandler<S>()
+        val successes = loadServices<S>(failures)
+        val allFailures = failures.toCollection()
+        assertAll(
+            {
+                assertEquals(successMessage, successes.toString()) {
+                    "Successfully loaded services for ${S::class.java.name}"
+                }
+            },
+            {
+                assertEquals(
+                    failureMessage,
+                    allFailures.map {
+                        "service: ${it.service.name}, " +
+                            "className: ${it.className}, " +
+                            "exceptionClass: ${it.throwable::class.java.name}, " +
+                            "causeClass: ${it.throwable.cause?.let { it::class.java.name }}"
+                    }.toString(),
+                ) {
+                    "All failures when loading service ${S::class.java.name} are $allFailures"
+                }
+            }
+        )
+    }
+}
diff --git a/xdocs/changes.xml b/xdocs/changes.xml
index 4ea3946381..f212ecfbf5 100644
--- a/xdocs/changes.xml
+++ b/xdocs/changes.xml
@@ -103,6 +103,7 @@ Summary
   <li><pr>5792</pr>Add KeyStroke for start_no_timers (Start no pauses: CRTL+SHIFT+n)</li>
   <li><pr>5899</pr>Speed up CPU-bound tests by skipping <code>recoverRunningVersion</code> for elements that are shared between threads (the ones that implement <code>NoThreadClone</code>)</li>
   <li><pr>5914</pr>Use <code>Locale.ROOT</code> instead of default locale for <code>toUpperCase</code>, and <code>toLowerCase</code> to avoid surprises with dotless I in <code>tr_TR</code> locale</li>
+  <li><pr>5885</pr>Use Java's <code>ServiceLoader</code> for loading plugins instead of classpath scanning. It enables faster startup</li>
 </ul>
 
 <ch_section>Non-functional changes</ch_section>
diff --git a/xdocs/usermanual/jmeter_tutorial.xml b/xdocs/usermanual/jmeter_tutorial.xml
index f32a8d0f88..04dfec7578 100644
--- a/xdocs/usermanual/jmeter_tutorial.xml
+++ b/xdocs/usermanual/jmeter_tutorial.xml
@@ -168,7 +168,49 @@ write.
 
 </subsection>
 
-<subsection name="&sect-num;.2 JMeter Gui – TestElement Contract" anchor="testelement-contract">
+  <subsection name="&sect-num;.2 JMeter Gui – Service lookup" anchor="service-lookup">
+    <p>
+      JMeter uses classpath scanning to detect plugins (e.g. functions, components, views). However, classpath scanning
+      is expensive,
+      so JMeter also provides a service lookup mechanism to allow plugins to be found without scanning the classpath.
+      That is if a plugin registers a Java service with <code>META-INF/services</code>, then JMeter won't need to scan
+      the classpath to find it.
+    </p>
+
+    <p>
+      Some of the existing features already use the new service lookup mechanism, but it is not yet used for all
+      features.
+      The interfaces that are supported for service loading are marked with
+      <code>org.apache.jorphan.reflect.JMeterService</code>
+      annotation.
+    </p>
+
+    <p>
+      Implementing a service the same as regular interface implementation, except you need to register the service
+      in a <code>META-INF/services/fully.qualified.interface.name</code>.
+      For instance you could use <code>@AutoService</code> to generate the file automatically at the build time.
+      Here's how <code>__counter</code> function is declared in JMeter itself:
+<source>
+import com.google.auto.service.AutoService;
+
+@AutoService(Function.class)
+public class IterationCounter extends AbstractFunction implements ThreadListener {
+</source>
+    </p>
+
+    <p>
+      For backward compatibility reasons, JMeter would still try searching the implementations with classpath scanning,
+      so you don't have to use <code>META-INF/services</code> for registering services. However, service lookup is much
+      faster, so exposing services would improve startup time, especially when there are many plugins.
+    </p>
+
+    <p>
+      As you add <code>META-INF/services</code> to your plugins, you can add <code>JMeter-Skip-Class-Scanning: true</code>
+      manifest attribute so JMeter knows there's no need to scan the jar as it provides all the plugins via services.
+    </p>
+  </subsection>
+
+<subsection name="&sect-num;.3 JMeter Gui – TestElement Contract" anchor="testelement-contract">
 
 <p>
 When writing any JMeter component, there are certain contracts you must be aware of – ways a
@@ -327,7 +369,7 @@ Writing Visualizers is somewhat of a special case, however.
 
 </subsection>
 
-<subsection name="&sect-num;.3 Writing a Visualizer" anchor="visualizer">
+<subsection name="&sect-num;.4 Writing a Visualizer" anchor="visualizer">
 <note>Load Testing in GUI mode being a bad practice, you should not develop such plugin. Have
 a look at more up to date components like:
 <ul>
@@ -537,7 +579,7 @@ added so that users can save a screen capture of any given visualizer.
 
 </subsection>
 
-<subsection name="&sect-num;.4 GraphListener" anchor="graphlistener">
+<subsection name="&sect-num;.5 GraphListener" anchor="graphlistener">
 <note>Load Testing in GUI mode being a bad practice, you should not develop such plugin. Have
 a look at more up to date components like:
 <ul>
@@ -571,7 +613,7 @@ implement the interface, please look at <code>GraphVisualizer</code>.
 
 </subsection>
 
-<subsection name="&sect-num;.5 Writing Custom Graphs" anchor="custom-graphs">
+<subsection name="&sect-num;.6 Writing Custom Graphs" anchor="custom-graphs">
 <note>Load Testing in GUI mode being a bad practice, you should not develop such plugin. Have
 a look at more up to date components like:
 <ul>
@@ -715,7 +757,7 @@ and predictable.
 
 </subsection>
 
-<subsection name="&sect-num;.6 Making a TestBean Plugin For JMeter" anchor="testbean">
+<subsection name="&sect-num;.7 Making a TestBean Plugin For JMeter" anchor="testbean">
 
 <p>
 In this part, we will go through the process of creating a simple component for JMeter that uses
@@ -860,7 +902,7 @@ p.setPropertyEditorClass(FileEditor.class);
 
 </subsection>
 
-<subsection name="&sect-num;.6 Building JMeter" anchor="building">
+<subsection name="&sect-num;.8 Building JMeter" anchor="building">
 
 <p>
 JMeter uses Gradle to compile and build the distribution. JMeter has