Uship (µship) embed Tomcat in your application enabling you to fully control it with almost no abstraction.

Modes

µship comes with two Tomcat modules:

  • webserver-tomcat which is a thin Apache Tomcat wrapper creating - by default - a ROOT context,

  • webserver-cdi which integrated webserver-tomcat with CDI.

Standalone mode

The standalone mode (webserver-tomcat) is mainly about creating a TomcatWebServerConfiguration and instantiating a TomcatWebServer:

// (1)
final var configuration = new TomcatWebServerConfiguration();
configuration.setPort(0); // random
configuration.setInitializers(List.of((set, servletContext) -> // (2)
servletContext.addServlet("test", new HttpServlet() {
    @Override
    protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
        resp.getWriter().write("ok from servlet");
    }
}).addMapping("/test")));

try (final var server = new TomcatWebServer(configuration).create()) { // (3)
    // now configuration.getPort() contains the runtime port since it was requested to be random (0) // (4)

    // use the webserver
}
  1. Create a configuration instance,

  2. You can bind any servlet, filter etc using initializers,

  3. Create a server (create call is what starts the server) and don’t forget to close is when no more needed (done with try-with-resource syntax there),

  4. The port can be random using 0 in the original configuration, it will be updated after the startup of the server in this case.

Important
default Tomcat scanning (@WebServlet etc) is not enabled - you will see that with CDI it is rarely needed - but you can enable it adding a context customizer registering ContextConfig:

+

context.addLifecycleListener(new ContextConfig());

CDI Mode

CDI mode is almost the same as standalone mode except:

  • You can (optional) produce TomcatWebServerConfiguration in CDI context:

    @Dependent
    public class TomcatConfigurationProducer {
        // (1)
        @Produces
        @ApplicationScoped
        public TomcatWebServerConfiguration tomcatWebServerConfiguration() {
            final TomcatWebServerConfiguration tomcat = new TomcatWebServerConfiguration(configuration); // (2)
            tomcat.setContextCustomizers(List.of(ctx -> { // (3)
                // ...
            }));
            return tomcat;
        }
    }
    1. It can be a standard subclass of the POJO TomcatWebServerConfiguration or like here a producer, in all cases it is recommended to use @ApplicationScoped even if not required to ensure the instance is shared between injection if you reuse it soewhere else (like in tests or in a servlet),

    2. Create the configuration (same as standalone case), here te trick is generally to reuse the native configuration mecanism of the application (microprofile config for example),

    3. Add a context customizer to customize the docbase, context name etc…​

  • You can (optional) create ServletContextInitializer, ContextCustomizer and TomcatCustomizer beans (with @Default qualifier) which will automatically be injected in the TomcatWebServerConfiguration.

    @Dependent
    public class ServletRegistrations implements ServletContainerInitializer {
        @Inject
        private HealthServlet health;
    
        @Inject
        private MetricsServlet metrics;
    
        @Override
        public void onStartup(final Set<Class<?>> ignored, final ServletContext servletContext) {
            final var metricsBinding = servletContext.addServlet("metrics", metrics);
            metricsBinding.addMapping("/metrics");
            metricsBinding.setLoadOnStartup(0);
    
            final var healthBinding = servletContext.addServlet("health", health);
            healthBinding.addMapping("/health");
            healthBinding.setLoadOnStartup(0);
        }
    }
    Tip
    no need of a META-INF/services/jakarta.servlet.ServletContainerInitializer file in this case, CDI is the registry used.

(Open) Tracing

tracing module provides a Tomcat valve you can set up on your web container to add tracing capabilities to your Tomcat:

configuration.setContextCustomizers(List.of(c -> c.getPipeline() (1)
    .addValve(new TracingValve( (1)
        new ServerTracingConfiguration(), (2)
        new AccumulatingSpanCollector().setOnFlush(...), (3)
        new IdGenerator(IdGenerator.Type.HEX), (4)
        systemUTC())))); (5)
  1. Add the valve to the context pipeline, it is recommended to add it as early as possible (just after error report and access log valve in general),

  2. The configuration enables to customize the span tags and headers to read for span propagation,

  3. The accumulator is what will send/log/…​ the spans once aggregated, ensure to configure it as needed,

  4. The IdGenerator provides the span/trace identifiers, it must be compatible with your collector (hex for zipkin for example),

  5. Finally the clock enables to timestamp the span and compute its duration.

Important
if you reuse AccumulatingSpanCollector, it is automatically closed with the valve "stop" phase. You can combine the accumulator with ZipkinFlusher onFlush implementation to flush to a zipkin collector v2.