Set up production-grade database connection pooling in Tomcat 11 using JNDI resources and HikariCP for optimal performance and high availability. Learn to configure server.xml, context.xml, and monitor connection pools effectively.
Prerequisites
- Root or sudo access
- Basic knowledge of Java web applications
- Database server (PostgreSQL or MySQL) running
What this solves
Database connection pooling prevents your Tomcat applications from creating expensive database connections for every request. This tutorial shows you how to configure Tomcat 11 with JNDI resources and HikariCP connection pooling for production workloads that require high availability and optimal database performance.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest versions of all dependencies.
sudo apt update && sudo apt upgrade -y
Install Java 21 and Tomcat 11
Tomcat 11 requires Java 11 or higher. We'll install OpenJDK 21 for optimal performance and compatibility.
sudo apt install -y openjdk-21-jdk wget
Download and install Tomcat 11
Download the latest Tomcat 11 release and install it to a dedicated directory with proper ownership.
cd /opt
sudo wget https://archive.apache.org/dist/tomcat/tomcat-11/v11.0.0/bin/apache-tomcat-11.0.0.tar.gz
sudo tar xzf apache-tomcat-11.0.0.tar.gz
sudo mv apache-tomcat-11.0.0 tomcat11
sudo useradd -r -s /bin/false tomcat
sudo chown -R tomcat:tomcat /opt/tomcat11
Install database drivers
Download the JDBC drivers for your database and place them in Tomcat's lib directory. This example uses PostgreSQL and MySQL drivers.
cd /opt/tomcat11/lib
sudo wget https://jdbc.postgresql.org/download/postgresql-42.7.1.jar
sudo wget https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-j-8.2.0.jar
sudo chown tomcat:tomcat *.jar
Download HikariCP connection pool library
HikariCP provides superior performance compared to Tomcat's default DBCP. Download the latest version to Tomcat's lib directory.
cd /opt/tomcat11/lib
sudo wget https://repo1.maven.org/maven2/com/zaxxer/HikariCP/5.1.0/HikariCP-5.1.0.jar
sudo chown tomcat:tomcat HikariCP-5.1.0.jar
Configure JNDI database resources in server.xml
Add database connection pool configurations to Tomcat's server.xml file. This creates global JNDI resources available to all applications.
<GlobalNamingResources>
<!-- PostgreSQL Connection Pool -->
<Resource name="jdbc/PostgresDB"
auth="Container"
type="javax.sql.DataSource"
factory="com.zaxxer.hikari.HikariJNDIFactory"
jdbcUrl="jdbc:postgresql://localhost:5432/myapp"
username="dbuser"
password="securepassword123"
driverClassName="org.postgresql.Driver"
maximumPoolSize="20"
minimumIdle="5"
connectionTimeout="30000"
idleTimeout="600000"
maxLifetime="1800000"
leakDetectionThreshold="60000" />
<!-- MySQL Connection Pool -->
<Resource name="jdbc/MySQLDB"
auth="Container"
type="javax.sql.DataSource"
factory="com.zaxxer.hikari.HikariJNDIFactory"
jdbcUrl="jdbc:mysql://localhost:3306/myapp"
username="dbuser"
password="securepassword123"
driverClassName="com.mysql.cj.jdbc.Driver"
maximumPoolSize="25"
minimumIdle="5"
connectionTimeout="30000"
idleTimeout="600000"
maxLifetime="1800000"
leakDetectionThreshold="60000"
cachePrepStmts="true"
prepStmtCacheSize="250"
prepStmtCacheSqlLimit="2048" />
</GlobalNamingResources>
Configure resource links in context.xml
Create resource links in the global context.xml to make JNDI resources available to web applications.
<Context>
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource>
<WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>
<!-- Link to global JNDI resources -->
<ResourceLink name="jdbc/PostgresDB"
global="jdbc/PostgresDB"
type="javax.sql.DataSource" />
<ResourceLink name="jdbc/MySQLDB"
global="jdbc/MySQLDB"
type="javax.sql.DataSource" />
</Context>
Optimize HikariCP configuration for high availability
Update the server.xml with production-optimized HikariCP settings for better performance and reliability.
<!-- Production-optimized PostgreSQL Pool -->
<Resource name="jdbc/PostgresDB"
auth="Container"
type="javax.sql.DataSource"
factory="com.zaxxer.hikari.HikariJNDIFactory"
jdbcUrl="jdbc:postgresql://localhost:5432/myapp?prepareThreshold=0&preparedStatementCacheQueries=256&preparedStatementCacheSizeMiB=5"
username="dbuser"
password="securepassword123"
driverClassName="org.postgresql.Driver"
maximumPoolSize="30"
minimumIdle="10"
connectionTimeout="20000"
idleTimeout="300000"
maxLifetime="1200000"
leakDetectionThreshold="30000"
validationTimeout="5000"
connectionTestQuery="SELECT 1"
initializationFailTimeout="1"
isolateInternalQueries="true"
allowPoolSuspension="true"
readOnly="false"
registerMbeans="true" />
Create systemd service for Tomcat
Create a systemd service file to manage Tomcat as a system service with proper Java memory settings.
[Unit]
Description=Apache Tomcat 11 Web Application Container
After=network.target
[Service]
Type=forking
Environment=JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
Environment=CATALINA_PID=/opt/tomcat11/temp/tomcat.pid
Environment=CATALINA_HOME=/opt/tomcat11
Environment=CATALINA_BASE=/opt/tomcat11
Environment='CATALINA_OPTS=-Xms1024M -Xmx2048M -server -XX:+UseG1GC'
Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom -Dcom.zaxxer.hikari.housekeeping.periodMs=30000'
ExecStart=/opt/tomcat11/bin/startup.sh
ExecStop=/opt/tomcat11/bin/shutdown.sh
User=tomcat
Group=tomcat
UMask=0007
RestartSec=10
Restart=always
[Install]
WantedBy=multi-user.target
Configure connection pool monitoring
Enable JMX monitoring for HikariCP by adding JMX configuration to the Tomcat service.
sudo mkdir -p /opt/tomcat11/conf/Catalina/localhost
sudo tee /opt/tomcat11/conf/Catalina/localhost/manager.xml > /dev/null <<EOF
<Context privileged="true" antiResourceLocking="false"
docBase="\${catalina.home}/webapps/manager">
<CookieProcessor className="org.apache.tomcat.util.http.Rfc6265CookieProcessor"
sameSiteCookies="strict" />
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.0\.0\.1|::1|0:0:0:0:0:0:0:1" />
<Manager sessionAttributeValueClassNameFilter="java\.lang\.(?:Boolean|Integer|Long|Number|String)|org\.apache\.catalina\.filters\.CsrfPreventionFilter\$LruCache(?:\$[12])?|java\.util\.(?:Linked)?HashMap"/>
</Context>
EOF
sudo chown tomcat:tomcat /opt/tomcat11/conf/Catalina/localhost/manager.xml
Configure web.xml for connection pool usage
Update your application's web.xml to reference the JNDI database resources.
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<resource-ref>
<description>PostgreSQL Database Connection</description>
<res-ref-name>jdbc/PostgresDB</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<resource-ref>
<description>MySQL Database Connection</description>
<res-ref-name>jdbc/MySQLDB</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</web-app>
Enable and start Tomcat service
Reload systemd configuration and start the Tomcat service with connection pooling enabled.
sudo systemctl daemon-reload
sudo systemctl enable tomcat
sudo systemctl start tomcat
sudo systemctl status tomcat
Verify your setup
Check that Tomcat is running correctly and the connection pools are properly configured.
sudo systemctl status tomcat
sudo ss -tlnp | grep 8080
sudo tail -f /opt/tomcat11/logs/catalina.out
Test JNDI resource availability by checking the logs for HikariCP initialization messages:
sudo grep -i hikari /opt/tomcat11/logs/catalina.out
curl -I http://localhost:8080
Monitor connection pool statistics through JMX or application logs:
sudo netstat -an | grep 5432
sudo netstat -an | grep 3306
Configure connection pool monitoring
Enable JMX monitoring
Add JMX configuration to monitor HikariCP pools in production environments.
Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom -Dcom.zaxxer.hikari.housekeeping.periodMs=30000 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false'
Create connection pool health check
Implement a simple Java servlet to monitor connection pool health and availability.
<%@ page import="javax.naming., javax.sql., java.sql.*" %>
<%
response.setContentType("application/json");
try {
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/PostgresDB");
Connection conn = ds.getConnection();
if (conn != null && !conn.isClosed()) {
conn.close();
out.println("{\"status\":\"healthy\",\"database\":\"connected\"}");
} else {
response.setStatus(503);
out.println("{\"status\":\"unhealthy\",\"database\":\"disconnected\"}");
}
} catch (Exception e) {
response.setStatus(503);
out.println("{\"status\":\"error\",\"message\":\"" + e.getMessage() + "\"}");
}
%>
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| ClassNotFoundException for HikariCP | HikariCP JAR not in lib directory | Ensure HikariCP JAR is in /opt/tomcat11/lib/ |
| Connection timeout errors | Database server unreachable | Check database server status and network connectivity |
| Pool exhausted exceptions | maximumPoolSize too small | Increase maximumPoolSize in server.xml based on load |
| Memory leaks on restart | Connections not properly closed | Enable leakDetectionThreshold and fix application code |
| Authentication failures | Incorrect database credentials | Verify username/password in JNDI resource configuration |
| JNDI lookup failures | Resource links not configured | Ensure ResourceLink entries in context.xml match server.xml names |
Next steps
- Setup Tomcat 10 clustering with HAProxy load balancing for high availability
- Configure PostgreSQL 17 streaming replication for high availability with automatic failover
- Implement MariaDB connection pooling with ProxySQL for high availability
- Configure Tomcat 11 SSL certificates and security hardening for production
- Monitor Tomcat application performance with Prometheus and Grafana dashboards
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
# Default values
DB_HOST="localhost"
DB_NAME="myapp"
DB_USER="dbuser"
DB_PASS="securepassword123"
# Usage function
usage() {
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " --db-host HOST Database host (default: localhost)"
echo " --db-name NAME Database name (default: myapp)"
echo " --db-user USER Database username (default: dbuser)"
echo " --db-pass PASS Database password (default: securepassword123)"
echo " -h, --help Show this help message"
exit 1
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--db-host) DB_HOST="$2"; shift 2 ;;
--db-name) DB_NAME="$2"; shift 2 ;;
--db-user) DB_USER="$2"; shift 2 ;;
--db-pass) DB_PASS="$2"; shift 2 ;;
-h|--help) usage ;;
*) echo "Unknown option: $1"; usage ;;
esac
done
# Cleanup function
cleanup() {
if [[ -f /opt/tomcat11.tar.gz ]]; then
rm -f /opt/tomcat11.tar.gz
fi
}
# Error handler
error_handler() {
echo -e "${RED}[ERROR] Installation failed at step $1${NC}"
cleanup
exit 1
}
trap 'error_handler $LINENO' ERR
# Check if running as root or with sudo
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root or with sudo${NC}"
exit 1
fi
echo -e "${GREEN}Starting Tomcat 11 HikariCP installation...${NC}"
# Auto-detect distro
echo -e "${YELLOW}[1/9] Detecting operating system...${NC}"
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
JAVA_PKG="openjdk-21-jdk"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
JAVA_PKG="java-21-openjdk-devel"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
JAVA_PKG="java-21-openjdk-devel"
;;
*)
echo -e "${RED}Unsupported distro: $ID${NC}"
exit 1
;;
esac
echo -e "${GREEN}Detected: $PRETTY_NAME${NC}"
else
echo -e "${RED}Cannot detect operating system${NC}"
exit 1
fi
# Update system packages
echo -e "${YELLOW}[2/9] Updating system packages...${NC}"
$PKG_UPDATE
# Install Java 21 and dependencies
echo -e "${YELLOW}[3/9] Installing Java 21 and dependencies...${NC}"
$PKG_INSTALL $JAVA_PKG wget tar
# Download and install Tomcat 11
echo -e "${YELLOW}[4/9] Downloading and installing Tomcat 11...${NC}"
cd /opt
wget -q https://archive.apache.org/dist/tomcat/tomcat-11/v11.0.0/bin/apache-tomcat-11.0.0.tar.gz -O tomcat11.tar.gz
tar xzf tomcat11.tar.gz
mv apache-tomcat-11.0.0 tomcat11
useradd -r -s /bin/false tomcat 2>/dev/null || true
chown -R tomcat:tomcat /opt/tomcat11
chmod -R 755 /opt/tomcat11
# Install database drivers
echo -e "${YELLOW}[5/9] Installing database drivers...${NC}"
cd /opt/tomcat11/lib
wget -q https://jdbc.postgresql.org/download/postgresql-42.7.1.jar
wget -q https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-j-8.2.0.jar
chown tomcat:tomcat *.jar
chmod 644 *.jar
# Download HikariCP library
echo -e "${YELLOW}[6/9] Installing HikariCP library...${NC}"
wget -q https://repo1.maven.org/maven2/com/zaxxer/HikariCP/5.1.0/HikariCP-5.1.0.jar
chown tomcat:tomcat HikariCP-5.1.0.jar
chmod 644 HikariCP-5.1.0.jar
# Configure server.xml
echo -e "${YELLOW}[7/9] Configuring server.xml with JNDI resources...${NC}"
cp /opt/tomcat11/conf/server.xml /opt/tomcat11/conf/server.xml.backup
sed -i '/<\/GlobalNamingResources>/i\
<!-- PostgreSQL Connection Pool -->\
<Resource name="jdbc/PostgresDB"\
auth="Container"\
type="javax.sql.DataSource"\
factory="com.zaxxer.hikari.HikariJNDIFactory"\
jdbcUrl="jdbc:postgresql://'$DB_HOST':5432/'$DB_NAME'"\
username="'$DB_USER'"\
password="'$DB_PASS'"\
driverClassName="org.postgresql.Driver"\
maximumPoolSize="20"\
minimumIdle="5"\
connectionTimeout="30000"\
idleTimeout="600000"\
maxLifetime="1800000"\
leakDetectionThreshold="60000" />\
\
<!-- MySQL Connection Pool -->\
<Resource name="jdbc/MySQLDB"\
auth="Container"\
type="javax.sql.DataSource"\
factory="com.zaxxer.hikari.HikariJNDIFactory"\
jdbcUrl="jdbc:mysql://'$DB_HOST':3306/'$DB_NAME'"\
username="'$DB_USER'"\
password="'$DB_PASS'"\
driverClassName="com.mysql.cj.jdbc.Driver"\
maximumPoolSize="25"\
minimumIdle="5"\
connectionTimeout="30000"\
idleTimeout="600000"\
maxLifetime="1800000"\
leakDetectionThreshold="60000"\
cachePrepStmts="true"\
prepStmtCacheSize="250"\
prepStmtCacheSqlLimit="2048" />
' /opt/tomcat11/conf/server.xml
# Configure context.xml
echo -e "${YELLOW}[8/9] Configuring context.xml with resource links...${NC}"
cp /opt/tomcat11/conf/context.xml /opt/tomcat11/conf/context.xml.backup
sed -i '/<\/Context>/i\
<!-- Link to global JNDI resources -->\
<ResourceLink name="jdbc/PostgresDB" global="jdbc/PostgresDB" type="javax.sql.DataSource"/>\
<ResourceLink name="jdbc/MySQLDB" global="jdbc/MySQLDB" type="javax.sql.DataSource"/>
' /opt/tomcat11/conf/context.xml
# Create systemd service
echo -e "${YELLOW}[9/9] Creating systemd service...${NC}"
cat > /etc/systemd/system/tomcat11.service << 'EOF'
[Unit]
Description=Apache Tomcat 11
After=network.target
[Service]
Type=forking
User=tomcat
Group=tomcat
Environment="JAVA_HOME=/usr/lib/jvm/java-21-openjdk"
Environment="CATALINA_HOME=/opt/tomcat11"
Environment="CATALINA_BASE=/opt/tomcat11"
Environment="CATALINA_PID=/opt/tomcat11/temp/tomcat.pid"
Environment="CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC"
ExecStart=/opt/tomcat11/bin/startup.sh
ExecStop=/opt/tomcat11/bin/shutdown.sh
[Install]
WantedBy=multi-user.target
EOF
# Set correct permissions and start service
chown tomcat:tomcat /opt/tomcat11/conf/*.xml
chmod 644 /opt/tomcat11/conf/*.xml
systemctl daemon-reload
systemctl enable tomcat11
systemctl start tomcat11
# Cleanup
cleanup
# Verification checks
echo -e "${YELLOW}Verifying installation...${NC}"
sleep 5
if systemctl is-active --quiet tomcat11; then
echo -e "${GREEN}✓ Tomcat 11 is running${NC}"
else
echo -e "${RED}✗ Tomcat 11 failed to start${NC}"
exit 1
fi
if curl -s http://localhost:8080 > /dev/null; then
echo -e "${GREEN}✓ Tomcat web interface is accessible${NC}"
else
echo -e "${YELLOW}⚠ Tomcat web interface check failed (may need time to start)${NC}"
fi
echo -e "${GREEN}Installation completed successfully!${NC}"
echo -e "Access Tomcat at: http://localhost:8080"
echo -e "Service management:"
echo -e " Start: systemctl start tomcat11"
echo -e " Stop: systemctl stop tomcat11"
echo -e " Status: systemctl status tomcat11"
echo -e " Logs: journalctl -u tomcat11 -f"
Review the script before running. Execute with: bash install.sh