When adding a jolokia-core dependency to a WebFlux (not MVC) Web Application, the Jolokia actuator endpoint is not being enabled because the application type condition match is failing.

JolokiaEndpointAutoConfiguration": {
          "notMatched": [
            {
              "condition": "OnWebApplicationCondition",
              "message": "not a servlet web application"
            }
          ],
          "matched": [
            {
              "condition": "OnClassCondition",
              "message": "@ConditionalOnClass found required class 'org.jolokia.http.AgentServlet'"
            }
          ]
        },

Is there a way to make it work in a WebFlux app?

Comment From: mbhave

@aantono The JolokiaEndpoint exposes Jolokia's servlet as an actuator endpoint and is specific to servlet environments such as Spring MVC and Jersey.

Can you expand on why you want the Jolokia actuator in a WebFlux application?

Comment From: aantono

I'm building a project based on Spring Cloud Gateway, which uses WebFlux... but I would like to rely on Jolokia JMX over HTTP via Actuator endpoint to be able to control various settings, and was very surprised to discover that it is not available in WebFlux type webapps...

Can you elaborate as to why having Jolokia actuator endpoint exposed in WebFlux applications is not a reasonable expectation?

Comment From: spencergibb

It's a servlet. Servlets aren't compatible with webflux.

Comment From: snicoll

@aantono if you want to be able to use jolokia outside of a servlet environment, please create an issue in their tracker.

Comment From: aantono

@snicoll, in all honesty, I find it a bit of a weak argument to not support JMX exposing endpoint purely because Jolokia is based on a Servlet implementation. There is also support for a generic HttpRequestHandler, which is not tied to a Servlet API, which has been available since 2010. The AgentServlet is mostly wrapping the HttpRequestHandler call with ServletAPI specific transformations, as evident from https://github.com/rhuss/jolokia/blob/master/agent/core/src/main/java/org/jolokia/http/AgentServlet.java#L305, which can be done the same way in the Spring Boot Actuator Endpoint implementation (so no need to attach one-self to the ServletAPI).

Comment From: snicoll

I find it a bit of a weak argument to not support JMX exposing endpoint purely because Jolokia is based on a Servlet implementation.

That's not the argument that has been discussed here though. You were asking how to use Jolokia in a WebFlux application and we've indicated that Jolokia doesn't support it so there is nothing we can do about it but perhaps they can. Please raise an issue on their end for a start and let's see how it goes.

Comment From: philwebb

Looking in a bit more detail at the HttpRequestHandler class that @aantono referenced above it appears that Jolokia is already written in such a way that it could work without servlets. Specifically, this javadoc seems to suggest that this is the case:

Request handler with no dependency on the servlet API so that it can be used in several different environment

I don't think it's likely that Jolokia could provide a non-blocking API. It's probably impossible anyway since JMX calls are likely to block.

It looks to me like we might be able to support this, however it's quite a lot of work. We'd need to:

  • Recreate code from AgentServlet that calls HttpRequestHandler
  • Create a new JolokiaEndpoint for WebFlux that doesn't use the servlet
  • Ensure that blocking calls are performed on the elastic scheduler

Comment From: philwebb

I'm afraid implementing this doesn't currently seem like a good investment of time on our part. Our current Jolokia servlet support is just a few lines of code and any WebFlux alternative will take quite an investment of time.

I've left this open in case anyone from the community is interested in attempting this. I'm not going to flag it as "ideal-for-contribution" because I fully expect the implementation to be quite involved.

Comment From: andrashatvani

I've just created https://github.com/rhuss/jolokia/issues/437.

Comment From: taxone

Does an alternative exist for exposing JXM beans using WebFlux (different from Jolokia)?

Comment From: andrashatvani

Does an alternative exist for exposing JXM beans using WebFlux (different from Jolokia)?

I use Micrometer with Prometheus now

Comment From: taxone

I implemented a POC of WebFlux Jolokia integration. Probably it's not complete and has some flaws but it seems to work.

Comments and suggestions in order to improve it are appreciated.

Comment From: rfelgent

Hello,

here are my 2 cents:

According to the Jolokia documentation you can run jolokia as java agent. This solution is not compatible with the 'actuator/jolokia' endpoint approach, but it works on any JVM process. Your app even doesn't not be a "web app", as the Jolokia Agent approach spins up a JDK based HttpServer itself. Therefore, the HttpServer can coexist with the reactive server.

Best regards

Comment From: viacheslav-dobrynin

@taxone, thank you!

I modified your code a little, which as a result allows me to use Spring Boot 3, WebFlux, Jolokia 2 and Spring Boot Admin. In particular, a custom actuator endpoint was created instead of a RestController:

import jakarta.annotation.PostConstruct
import org.jolokia.server.core.config.ConfigKey
import org.jolokia.server.core.config.StaticConfiguration
import org.jolokia.server.core.http.HttpRequestHandler
import org.jolokia.server.core.restrictor.RestrictorFactory
import org.jolokia.server.core.service.JolokiaServiceManagerFactory
import org.jolokia.server.core.service.api.LogHandler
import org.jolokia.server.core.service.api.Restrictor
import org.jolokia.server.core.service.impl.StdoutLogHandler
import org.jolokia.server.core.util.NetworkUtil
import org.jolokia.service.jmx.LocalRequestHandler
import org.jolokia.service.serializer.JolokiaSerializer
import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint
import org.springframework.http.server.reactive.ServerHttpRequest
import org.springframework.stereotype.Component
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.core.scheduler.Scheduler
import reactor.core.scheduler.Schedulers
import java.io.IOException

@Component
@RestControllerEndpoint(id = "jolokia")
class ReactiveJolokiaActuatorEndpoint {

    private lateinit var requestHandler: HttpRequestHandler
    private val scheduler: Scheduler = Schedulers.newBoundedElastic(3, 10, "jolokiaThreads")

    @PostConstruct
    fun init() {
        val config = StaticConfiguration(ConfigKey.AGENT_ID, NetworkUtil.getAgentId(hashCode(), "servlet"))
        val logHandler: LogHandler = StdoutLogHandler()
        val restrictor: Restrictor = RestrictorFactory.createRestrictor(config, logHandler)
        val serviceManager = JolokiaServiceManagerFactory.createJolokiaServiceManager(
            config,
            logHandler,
            restrictor,
        )
        serviceManager.addServices { setOf(JolokiaSerializer(), LocalRequestHandler(1000)) }
        val jolokiaContext = serviceManager.start()
        requestHandler = HttpRequestHandler(jolokiaContext)
    }

    @GetMapping(path = ["/**"])
    fun gioia(exchange: ServerWebExchange): Mono<String> = Mono.fromCallable {
        val pReq: ServerHttpRequest = exchange.request
        val pathInfo = pathInfo(pReq)
        requestHandler.handleGetRequest(pReq.uri.toString(), pathInfo, getParameterMap(pReq)).toJSONString()
    }.subscribeOn(scheduler)

    @PostMapping(path = ["/**"], consumes = ["application/json"])
    fun post(exchange: ServerWebExchange): Flux<String> = exchange.request.body.handle { body, sink ->
        val inputStream = body.asInputStream(true)
        val pReq: ServerHttpRequest = exchange.request
        try {
            sink.next(
                requestHandler.handlePostRequest(
                    pReq.uri.toString(),
                    inputStream,
                    null,
                    getParameterMap(pReq)
                ).toJSONString()
            )
        } catch (e: IOException) {
            sink.error(RuntimeException(e))
        }
    }.subscribeOn(scheduler)

    private fun pathInfo(pReq: ServerHttpRequest): String = pReq.path.subPath(4).toString()

    private fun getParameterMap(pReq: ServerHttpRequest): Map<String, Array<String>> =
        pReq.queryParams.entries.associate { it.key to it.value.toTypedArray() }
}

Gradle dependencies:

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("de.codecentric:spring-boot-admin-starter-client:3.1.8")

    implementation(platform("org.jolokia:jolokia-parent:2.0.0-M4"))
    implementation("org.jolokia:jolokia-server-core")
    implementation("org.jolokia:jolokia-service-jmx")
    implementation("org.jolokia:jolokia-service-serializer")
}

Maybe someone will find this useful.

Comment From: wilkinsona

The code that integrates Jolokia with Spring Boot is now part of Jolokia itself. If there's still a need for this, it should be raised with or contributed to the Jolokia project.