Set up efficient database connection pooling in Apache Tomcat 11 using HikariCP through JNDI configuration. Optimize connection parameters, implement proper resource management, and configure monitoring for production database workloads.
Prerequisites
- Root or sudo access
- Basic knowledge of Java web applications
- Database server (MySQL, PostgreSQL, etc.)
- At least 4GB RAM available
What this solves
Database connection pooling prevents your Tomcat applications from creating new database connections for every request, which quickly exhausts database resources and degrades performance. HikariCP provides the fastest and most reliable connection pooling implementation for Java applications, while JNDI configuration allows you to manage database connections at the container level rather than embedding them in your application code.
Step-by-step installation
Update system packages and install OpenJDK
Start by ensuring your system is up to date and install OpenJDK 17, which is the recommended Java version for Tomcat 11.
sudo apt update && sudo apt upgrade -y
sudo apt install -y openjdk-17-jdk wget curl unzip
Create Tomcat user and directories
Create a dedicated user for running Tomcat and set up the directory structure with proper ownership.
sudo useradd -r -s /bin/false -d /opt/tomcat tomcat
sudo mkdir -p /opt/tomcat
sudo mkdir -p /opt/tomcat/lib
Download and install Apache Tomcat 11
Download the latest Tomcat 11 release and extract it to the installation directory.
cd /tmp
wget https://downloads.apache.org/tomcat/tomcat-11/v11.0.1/bin/apache-tomcat-11.0.1.tar.gz
sudo tar -xzf apache-tomcat-11.0.1.tar.gz -C /opt/tomcat --strip-components=1
sudo chown -R tomcat:tomcat /opt/tomcat
sudo chmod +x /opt/tomcat/bin/*.sh
Download HikariCP library
Download the HikariCP JAR file and place it in Tomcat's lib directory so it's available for connection pooling.
cd /tmp
wget https://repo1.maven.org/maven2/com/zaxxer/HikariCP/5.1.0/HikariCP-5.1.0.jar
sudo cp HikariCP-5.1.0.jar /opt/tomcat/lib/
sudo chown tomcat:tomcat /opt/tomcat/lib/HikariCP-5.1.0.jar
Download database driver
Download the appropriate JDBC driver for your database. This example uses MySQL, but you can substitute with PostgreSQL or other database drivers.
wget https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/8.2.0/mysql-connector-j-8.2.0.jar
sudo cp mysql-connector-j-8.2.0.jar /opt/tomcat/lib/
sudo chown tomcat:tomcat /opt/tomcat/lib/mysql-connector-j-8.2.0.jar
Create systemd service file
Set up Tomcat as a system service with proper Java memory settings and system integration.
[Unit]
Description=Apache Tomcat 11
After=network.target
[Service]
Type=forking
User=tomcat
Group=tomcat
Environment="JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64"
Environment="JAVA_OPTS=-Djava.security.egd=file:///dev/urandom -Djava.awt.headless=true"
Environment="CATALINA_BASE=/opt/tomcat"
Environment="CATALINA_HOME=/opt/tomcat"
Environment="CATALINA_PID=/opt/tomcat/temp/tomcat.pid"
Environment="CATALINA_OPTS=-Xms2048M -Xmx4096M -server -XX:+UseParallelGC"
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
RestartSec=10
Restart=always
[Install]
WantedBy=multi-user.target
Configure context.xml for JNDI connection pooling
Configure the global JNDI resource for database connection pooling using HikariCP. This makes the connection pool available to all applications.
WEB-INF/web.xml
WEB-INF/tomcat-web.xml
${catalina.base}/conf/web.xml
Configure server.xml for optimized performance
Update the server.xml with optimized connector settings for high-performance database applications.
Set proper file permissions
Ensure all Tomcat files have the correct ownership and permissions for security and functionality.
sudo chown -R tomcat:tomcat /opt/tomcat
sudo chmod -R 755 /opt/tomcat
sudo chmod 600 /opt/tomcat/conf/context.xml
sudo chmod 600 /opt/tomcat/conf/server.xml
Enable and start Tomcat service
Enable the Tomcat service to start automatically on boot and start it now.
sudo systemctl daemon-reload
sudo systemctl enable tomcat
sudo systemctl start tomcat
Database-specific optimization settings
Configure MySQL optimizations
For MySQL databases, add these specific parameters to enhance performance and reliability with connection pooling.
Configure PostgreSQL optimizations
For PostgreSQL databases, use these optimized parameters that work well with HikariCP.
Performance monitoring and troubleshooting
Enable HikariCP monitoring
Configure HikariCP to expose JMX metrics for monitoring connection pool health and performance.
Configure JMX monitoring
Enable JMX remote monitoring by adding these Java options to your systemd service file.
Environment="CATALINA_OPTS=-Xms2048M -Xmx4096M -server -XX:+UseParallelGC -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
Create sample web application
Create a simple test application to verify your connection pool is working correctly.
Database Test App
Database Connection
jdbc/MyAppDB
javax.sql.DataSource
Container
Create test servlet
Create a simple servlet that tests database connectivity through the JNDI connection pool.
<%@ page import="java.sql.*" %>
<%@ page import="javax.sql.*" %>
<%@ page import="javax.naming.*" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Database Connection Test
HikariCP Connection Pool Test
<%
Connection conn = null;
Statement stmt = null;
try {
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/MyAppDB");
conn = ds.getConnection();
stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT 1 as test");
if (rs.next()) {
out.println("Database connection successful!
");
out.println("Test query result: " + rs.getInt("test") + "
");
}
rs.close();
} catch (Exception e) {
out.println("Database connection failed: " + e.getMessage() + "
");
} finally {
if (stmt != null) try { stmt.close(); } catch (SQLException e) {}
if (conn != null) try { conn.close(); } catch (SQLException e) {}
}
%>
Set test application permissions
Ensure the test application has proper ownership and can be deployed by Tomcat.
sudo mkdir -p /opt/tomcat/webapps/dbtest/WEB-INF
sudo chown -R tomcat:tomcat /opt/tomcat/webapps/dbtest
sudo systemctl restart tomcat
Verify your setup
Check that Tomcat is running and the connection pool is configured correctly.
sudo systemctl status tomcat
curl -I http://localhost:8080
ss -tlnp | grep 8080
tail -f /opt/tomcat/logs/catalina.out
Visit your test application to verify database connectivity:
curl http://localhost:8080/dbtest/
Monitor connection pool metrics through JMX:
jconsole localhost:8999
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Connection pool not found | JNDI resource name mismatch | Check resource name in context.xml matches web.xml lookup |
| Too many connections error | Pool size exceeds database limits | Reduce maximumPoolSize or increase database max_connections |
| Connection timeout errors | Network latency or database overload | Increase connectionTimeout and check database performance |
| Memory leaks in logs | Connections not properly closed | Enable leakDetectionThreshold and fix application code |
| ClassNotFoundException for HikariCP | JAR file missing from lib directory | Verify HikariCP JAR is in /opt/tomcat/lib/ |
| Database driver not found | JDBC driver missing or wrong version | Download correct driver version for your database |
Next steps
- Integrate Apache Tomcat 11 with Prometheus and Grafana monitoring for comprehensive performance tracking
- Configure MySQL connection pooling with ProxySQL 2.6 for high availability and performance optimization
- Set up HAProxy high availability with keepalived clustering for automatic failover
- Configure Tomcat 11 clustering with session replication for high availability
- Implement SSL termination with NGINX reverse proxy for Tomcat applications
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Global variables
TOMCAT_VERSION="11.0.1"
HIKARICP_VERSION="5.1.0"
MYSQL_CONNECTOR_VERSION="8.2.0"
DB_NAME="${1:-myapp}"
DB_USER="${2:-dbuser}"
DB_PASS="${3:-dbpass}"
DB_HOST="${4:-localhost}"
# Usage function
usage() {
echo "Usage: $0 [db_name] [db_user] [db_pass] [db_host]"
echo "Defaults: myapp dbuser dbpass localhost"
exit 1
}
# Error handling
error_exit() {
echo -e "${RED}[ERROR]${NC} $1" >&2
exit 1
}
success_msg() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
info_msg() {
echo -e "${YELLOW}[INFO]${NC} $1"
}
# Cleanup function
cleanup() {
info_msg "Cleaning up temporary files..."
rm -f /tmp/apache-tomcat-*.tar.gz /tmp/*.jar 2>/dev/null || true
}
trap cleanup EXIT ERR
# Check if running as root
if [[ $EUID -ne 0 ]]; then
error_exit "This script must be run as root"
fi
# Detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update && apt upgrade -y"
PKG_INSTALL="apt install -y"
JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
JAVA_PKG="openjdk-17-jdk"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
JAVA_HOME="/usr/lib/jvm/java-17-openjdk"
JAVA_PKG="java-17-openjdk-devel"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
JAVA_HOME="/usr/lib/jvm/java-17-openjdk"
JAVA_PKG="java-17-openjdk-devel"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
JAVA_HOME="/usr/lib/jvm/java-17-openjdk"
JAVA_PKG="java-17-openjdk-devel"
;;
*)
error_exit "Unsupported distribution: $ID"
;;
esac
else
error_exit "Cannot detect distribution"
fi
echo "[1/10] Updating system packages and installing OpenJDK 17..."
$PKG_UPDATE
$PKG_INSTALL $JAVA_PKG wget curl unzip tar
echo "[2/10] Creating Tomcat user and directories..."
if ! id "tomcat" &>/dev/null; then
useradd -r -s /bin/false -d /opt/tomcat tomcat
fi
mkdir -p /opt/tomcat/lib
echo "[3/10] Downloading and installing Apache Tomcat 11..."
cd /tmp
wget -q "https://downloads.apache.org/tomcat/tomcat-11/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz"
tar -xzf "apache-tomcat-${TOMCAT_VERSION}.tar.gz" -C /opt/tomcat --strip-components=1
chown -R tomcat:tomcat /opt/tomcat
chmod +x /opt/tomcat/bin/*.sh
echo "[4/10] Downloading HikariCP library..."
wget -q "https://repo1.maven.org/maven2/com/zaxxer/HikariCP/${HIKARICP_VERSION}/HikariCP-${HIKARICP_VERSION}.jar"
cp "HikariCP-${HIKARICP_VERSION}.jar" /opt/tomcat/lib/
chown tomcat:tomcat "/opt/tomcat/lib/HikariCP-${HIKARICP_VERSION}.jar"
echo "[5/10] Downloading MySQL JDBC driver..."
wget -q "https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/${MYSQL_CONNECTOR_VERSION}/mysql-connector-j-${MYSQL_CONNECTOR_VERSION}.jar"
cp "mysql-connector-j-${MYSQL_CONNECTOR_VERSION}.jar" /opt/tomcat/lib/
chown tomcat:tomcat "/opt/tomcat/lib/mysql-connector-j-${MYSQL_CONNECTOR_VERSION}.jar"
echo "[6/10] Creating systemd service file..."
cat > /etc/systemd/system/tomcat.service << EOF
[Unit]
Description=Apache Tomcat 11
After=network.target
[Service]
Type=forking
User=tomcat
Group=tomcat
Environment="JAVA_HOME=${JAVA_HOME}"
Environment="JAVA_OPTS=-Djava.security.egd=file:///dev/urandom -Djava.awt.headless=true"
Environment="CATALINA_BASE=/opt/tomcat"
Environment="CATALINA_HOME=/opt/tomcat"
Environment="CATALINA_PID=/opt/tomcat/temp/tomcat.pid"
Environment="CATALINA_OPTS=-Xms2048M -Xmx4096M -server -XX:+UseParallelGC"
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
RestartSec=10
Restart=always
[Install]
WantedBy=multi-user.target
EOF
echo "[7/10] Configuring context.xml for JNDI connection pooling..."
cat > /opt/tomcat/conf/context.xml << EOF
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource>
<WatchedResource>\${catalina.base}/conf/web.xml</WatchedResource>
<Resource name="jdbc/MyDataSource"
auth="Container"
type="javax.sql.DataSource"
factory="com.zaxxer.hikari.HikariJNDIFactory"
jdbcUrl="jdbc:mysql://${DB_HOST}:3306/${DB_NAME}?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"
username="${DB_USER}"
password="${DB_PASS}"
dataSource.cachePrepStmts="true"
dataSource.prepStmtCacheSize="250"
dataSource.prepStmtCacheSqlLimit="2048"
dataSource.useServerPrepStmts="true"
dataSource.useLocalSessionState="true"
dataSource.rewriteBatchedStatements="true"
dataSource.cacheResultSetMetadata="true"
dataSource.cacheServerConfiguration="true"
dataSource.elideSetAutoCommits="true"
dataSource.maintainTimeStats="false"
minimumIdle="5"
maximumPoolSize="20"
connectionTimeout="30000"
idleTimeout="600000"
maxLifetime="1800000"
leakDetectionThreshold="60000" />
</Context>
EOF
echo "[8/10] Configuring server.xml for optimized performance..."
cp /opt/tomcat/conf/server.xml /opt/tomcat/conf/server.xml.backup
cat > /opt/tomcat/conf/server.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxThreads="200"
minSpareThreads="10"
acceptCount="100"
enableLookups="false"
compression="on"
compressionMinSize="2048"
compressibleMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
EOF
echo "[9/10] Setting proper file permissions and ownership..."
chown -R tomcat:tomcat /opt/tomcat
find /opt/tomcat -type d -exec chmod 755 {} \;
find /opt/tomcat -type f -exec chmod 644 {} \;
chmod +x /opt/tomcat/bin/*.sh
chmod 600 /opt/tomcat/conf/context.xml
chmod 600 /opt/tomcat/conf/server.xml
echo "[10/10] Enabling and starting Tomcat service..."
systemctl daemon-reload
systemctl enable tomcat
systemctl start tomcat
# Verification
sleep 5
if systemctl is-active --quiet tomcat; then
success_msg "Tomcat service is running successfully"
else
error_exit "Tomcat service failed to start"
fi
if netstat -tuln 2>/dev/null | grep -q :8080 || ss -tuln 2>/dev/null | grep -q :8080; then
success_msg "Tomcat is listening on port 8080"
else
error_exit "Tomcat is not listening on port 8080"
fi
success_msg "Tomcat 11 with HikariCP connection pooling installed successfully!"
info_msg "Database configuration: ${DB_NAME}@${DB_HOST}"
info_msg "Access Tomcat at: http://your-server-ip:8080"
info_msg "Service commands:"
info_msg " Start: systemctl start tomcat"
info_msg " Stop: systemctl stop tomcat"
info_msg " Status: systemctl status tomcat"
info_msg " Logs: journalctl -u tomcat -f"
Review the script before running. Execute with: bash install.sh