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.xmlwith plain credentials. - Set
validationQueryso dead connections are dropped from the pool. - Log timings: wrap queries with
System.currentTimeMillis()or a proxy driver like p6spy during development. - Match
maxTotalto MySQL'smax_connectionsβ multiple Tomcat nodes Γ pool size must stay below the server limit.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| ClassNotFoundException: com.mysql.cj.jdbc.Driver | Driver JAR not in $TOMCAT_HOME/lib/ |
| NameNotFoundException: jdbc/MyApp | Missing resource-ref in web.xml |
| Public Key Retrieval is not allowed | Append &allowPublicKeyRetrieval=true to the JDBC URL |
| Access denied for user | Grant the user from the Tomcat host: GRANT ALL ON myapp.* TO 'appuser'@'%' |
| Too many connections | Pool 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.