Creating a Java Web Application Connection on Tomcat with MySQL

Configuring a Tomcat web application to talk to MySQL is a rite of passage. Done right, you get a pooled, thread-safe, container-managed connection. Done wrong, you leak handles and deadlock under load. This guide covers both the basic setup and the production-grade approach.

Prerequisites

  • Apache Tomcat 10 or later (Jakarta EE namespace)
  • MySQL 8 with a user and a database prepared
  • JDK 17+
  • Maven or Gradle for dependency management

Step 1 β€” The JDBC driver

Add the MySQL Connector/J to your pom.xml. Crucially, mark the scope provided because Tomcat will supply the driver itself.

<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
  <version>9.3.0</version>
  <scope>provided</scope>
</dependency>

Then drop the actual JAR into $TOMCAT_HOME/lib/. Placing it there (rather than inside the WAR) allows the connection pool to use it before your app starts.

Step 2 β€” Declare a DataSource in context.xml

Create src/main/webapp/META-INF/context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Context>
  <Resource name="jdbc/MyApp"
            auth="Container"
            type="javax.sql.DataSource"
            driverClassName="com.mysql.cj.jdbc.Driver"
            url="jdbc:mysql://localhost:3306/myapp?serverTimezone=UTC&useSSL=false"
            username="appuser"
            password="s3cret"
            maxTotal="20"
            maxIdle="5"
            maxWaitMillis="10000"
            validationQuery="SELECT 1"
            testOnBorrow="true" />
</Context>

On Tomcat 10+, the javax.sql.DataSource type is still used β€” only the namespaces of servlet and persistence APIs moved from javax to jakarta, not the DataSource API.

Step 3 β€” Reference the resource in web.xml

File: src/main/webapp/WEB-INF/web.xml

<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" version="5.0">
  <resource-ref>
    <res-ref-name>jdbc/MyApp</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
  </resource-ref>
</web-app>

Step 4 β€” Look up the DataSource from your servlet

import jakarta.servlet.http.*;
import jakarta.servlet.annotation.WebServlet;
import javax.naming.*;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.*;

@WebServlet("/users")
public class UserServlet extends HttpServlet {

    private DataSource dataSource;

    @Override
    public void init() {
        try {
            Context ctx = new InitialContext();
            dataSource = (DataSource) ctx.lookup("java:comp/env/jdbc/MyApp");
        } catch (NamingException e) {
            throw new IllegalStateException("Cannot locate jdbc/MyApp", e);
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        try (Connection conn = dataSource.getConnection();
             PreparedStatement ps = conn.prepareStatement("SELECT id, name FROM users");
             ResultSet rs = ps.executeQuery()) {

            resp.setContentType("application/json");
            StringBuilder out = new StringBuilder("[");
            while (rs.next()) {
                if (out.length() > 1) out.append(',');
                out.append("{\"id\":").append(rs.getInt("id"))
                   .append(",\"name\":\"").append(rs.getString("name")).append("\"}");
            }
            out.append(']');
            resp.getWriter().write(out.toString());
        } catch (SQLException e) {
            resp.sendError(500, e.getMessage());
        }
    }
}

The try-with-resources block guarantees that every connection, statement and result set is returned to the pool even on exception.

Tuning the pool

Tomcat's default pool (DBCP2) is sufficient for most apps, but for better performance, switch to HikariCP:

<Resource name="jdbc/MyApp"
          factory="com.zaxxer.hikari.HikariJNDIFactory"
          type="javax.sql.DataSource"
          jdbcUrl="jdbc:mysql://localhost:3306/myapp"
          username="appuser"
          password="s3cret"
          maximumPoolSize="20"
          minimumIdle="5" />

Drop HikariCP-*.jar into $TOMCAT_HOME/lib/ alongside the MySQL driver.

Best practices

  • Always use prepared statements β€” never concatenate SQL with user input, or you hand over SQL injection.
  • Externalise passwords β€” use environment variables or a secret store; never commit context.xml with plain credentials.
  • Set validationQuery so dead connections are dropped from the pool.
  • Log timings: wrap queries with System.currentTimeMillis() or a proxy driver like p6spy during development.
  • Match maxTotal to MySQL's max_connections β€” multiple Tomcat nodes Γ— pool size must stay below the server limit.

Troubleshooting

SymptomLikely cause
ClassNotFoundException: com.mysql.cj.jdbc.DriverDriver JAR not in $TOMCAT_HOME/lib/
NameNotFoundException: jdbc/MyAppMissing resource-ref in web.xml
Public Key Retrieval is not allowedAppend &allowPublicKeyRetrieval=true to the JDBC URL
Access denied for userGrant the user from the Tomcat host: GRANT ALL ON myapp.* TO 'appuser'@'%'
Too many connectionsPool size Γ— instances > MySQL max_connections

With the DataSource correctly registered, the pool handles reconnection, validation and concurrency on your behalf β€” your code stays focused on the SQL.