Mastodon

Cloudfoundry not recognizing your App as Spring Boot? Use empty Context Path!

I spend the bigger part of yesterday figuring out why my Spring Boot app was not recognized as such in Pivotal Cloudfoundry app manager. The Spring Boot logo was not displayed in front of the app name and I couldn’t configure the log levels or see the “Trace” and “Thread” tabs. Short answer: I used a custom context path. That doesn’t work with the Actuator integration in Cloudfoundry. Read more for details …

Spring Actuator and Cloudfoundry

Spring Actuator exposes HTTP endpoints in a Spring Boot application that can be used to control certain aspects of the app, like getting health information or changing the log level. Pivotal Cloudfoundry has a really nice integration for Actuator: The app manager, the web UI to control deployed apps, offers new tabs if a Spring Boot application with Actuator is recognized.

Here are sources to set up Actuator in Cloudfoundry:

App Not Recognized as Spring Boot by Cloudfoundry

There are many articles like this one describing setups in which Cloudfoundry is not able to recognize Spring Boot and/or Actuator. Even Pivotal kind of tries to help, but doesn’t provide the missing clue.

After a lot of searching, I found this final piece to the puzzle. To provide Actuator support, Cloudfoundry needs to be able to call the endpoint /cloudfoundryapplication. However, if you set a custom context path like this (in application.yml) …

server:
  port: 8090
  servlet:
    context-path: /myapp

… Cloudfoundry will not adapt the call to myapp/cloudfoundryapplication. Instead, you will see errors in the network tab of your browser developer tool.

There are two solutions:

  1. Use the default context path “/”.
  2. Add the following configuration, taken from the article above. It will make Tomcat provide the endpoint without a context:
@Bean
public TomcatServletWebServerFactory servletWebServerFactory() {
    return new TomcatServletWebServerFactory() {
 
        @Override
        protected void prepareContext(Host host,
                                      ServletContextInitializer[] initializers) {
            super.prepareContext(host, initializers);
            StandardContext child = new StandardContext();
            child.addLifecycleListener(new Tomcat.FixContextListener());
            child.setPath("/cloudfoundryapplication");
            ServletContainerInitializer initializer = getServletContextInitializer(
                    getContextPath());
            child.addServletContainerInitializer(initializer, Collections.emptySet());
            child.setCrossContext(true);
            host.addChild(child);
        }
 
    };
}
 
private ServletContainerInitializer getServletContextInitializer(String contextPath) {
    return (c, context) -> {
        Servlet servlet = new GenericServlet() {
 
            @Override
            public void service(ServletRequest req, ServletResponse res)
                    throws ServletException, IOException {
                ServletContext context = req.getServletContext()
                        .getContext(contextPath);
                context.getRequestDispatcher("/cloudfoundryapplication").forward(req,
                        res);
            }
 
        };
        context.addServlet("cloudfoundry", servlet).addMapping("/*");
    };
}

This has been really annoying. I hope this article is of help for others.