There are a few tutorials on how to run a web application using embedded Tomcat. For example:

They are all informative, and show various different configurations you can use.

And none of them work. At least, not the ones I looked at.

The main reason for this is because they were all written for versions of Tomcat prior to 9.0. From 9.0 onwards there are some breaking changes which need to be addressed:

Tomcat 9.0 (and 9.5)

In version 8.0 of org.apache.catalina.Context, the method addServletMapping was deprecated - and was subsequently removed from 9.0. Use one of the addServletMappingDecoded methods instead.

More fundamentally, in Tomcat 9.0.0 Milestone 14, the embedded Tomcat implementation was changed: Stop creating a default connector on start in embedded mode.

Before this change, you would see a start-up message such as:

INFO: Starting ProtocolHandler ["http-nio-8080"]

This indicates a connector, equivalent to that found in server.xml - similar to this:

1
<Connector port="8080" protocol="HTTP/1.1" />

That worked for Tomcat 8.0. But from 9.0 onwards, your embedded Tomcat will start, but there will be no default connectors created. Without any connectors, no URLs can be reached.

The fix is to explicitly create a connector, for example:

1
2
3
4
// Defaults to using HTTP/1.1 NIO implementation:
Connector conn = new Connector();
conn.setPort(8080);
tomcat.setConnector(conn);

Tomcat 10

For Tomcat 10, you need to update your javax packages to jakarta - and while this is not directly related to Tomcat (embedded or otherwise), it’s worth mentioning.

Here is a bare-bones Tomcat 10 example.

pom.xml dependencies:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<dependencies>
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-core</artifactId>
        <version>10.0.23</version>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
        <version>10.0.23</version>
    </dependency>
</dependencies>

One simple way to configure Tomcat with a servlet, using a single class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;

public class App {

    public static void main(String[] args) throws LifecycleException,
            InterruptedException, ServletException {

        Tomcat tomcat = new Tomcat();
        tomcat.setBaseDir("temp");
        Connector conn = new Connector();
        conn.setPort(8080);
        tomcat.setConnector(conn);
        String contextPath = "";
        String docBase = new File(".").getAbsolutePath();
        Context context = tomcat.addContext(contextPath, docBase);

        HttpServlet servlet = new HttpServlet() {
            @Override
            protected void doGet(HttpServletRequest req, HttpServletResponse resp)
                    throws ServletException, IOException {
                PrintWriter writer = resp.getWriter();
                writer.println("<html><title>Welcome</title><body>");
                writer.println("<h1>Hello world!</h1>");
                writer.println("</body></html>");
            }
        };

        String servletName = "Servlet1";
        String urlPattern = "/welcome";
        tomcat.addServlet(contextPath, servletName, servlet);
        context.addServletMappingDecoded(urlPattern, servletName);

        tomcat.start();
        tomcat.getServer().await();
    }
}

Displayed at http://localhost:8080/welcome.