When an application slows down or starts throwing "cannot get a connection" errors, the cause is very often the connection pool between the app server and the database. A pool that is too small queues requests. One that is too big floods the database with sessions. One without validation hands out dead connections after a database restart.
In this guide I install Apache Tomcat 9 on Linux, connect it to Oracle 19c through a JNDI datasource, and explain every pool setting from a DBA's point of view. Then I deploy a WAR and turn Tomcat into a proper systemd service.
| Component | Used here |
|---|---|
| OS | RHEL / Oracle Linux 8 or 9 |
| Tomcat | 9.0.x (latest patch release) |
| Java | OpenJDK 11 (Java 8 also works) |
| Database | Oracle 19c, service ORCLPDB1 |
| JDBC driver | ojdbc8.jar |
| Install path | /opt/tomcat/latest (= $CATALINA_HOME) |
STEP 1
Create a service user and install Java
Run Tomcat as its own unprivileged user, not as root and not as the oracle database owner. Tomcat 9 needs Java 8 or later.
sudo useradd -r -m -d /opt/tomcat -s /bin/bash tomcat sudo dnf install -y java-11-openjdk-headless # or java-1.8.0-openjdk-headless java -version
STEP 2
Download and unpack Tomcat 9
Download the latest 9.0.x "Core" tar.gz from tomcat.apache.org. A newer patch release always includes security fixes, so do not pin an old version. In the commands below, replace 9.0.x with the version you downloaded.
cd /opt/tomcat sudo -u tomcat tar -xzf /tmp/apache-tomcat-9.0.x.tar.gz sudo -u tomcat ln -s /opt/tomcat/apache-tomcat-9.0.x /opt/tomcat/latest export CATALINA_HOME=/opt/tomcat/latest ls $CATALINA_HOME # bin conf lib logs temp webapps work
The latest symlink lets you upgrade later by unpacking a new version and moving the link.
STEP 3
Set Java and memory in setenv.sh
Tomcat reads bin/setenv.sh at startup if it exists. This is the right place for your settings: it is never overwritten when you upgrade.
export JAVA_HOME=/usr/lib/jvm/jre-11-openjdk export CATALINA_OPTS="-Xms1g -Xmx2g -server -Djava.security.egd=file:/dev/urandom" export CATALINA_PID="$CATALINA_HOME/temp/tomcat.pid"
chmod 750 $CATALINA_HOME/bin/setenv.sh
STEP 4
Add the Oracle JDBC driver
Copy ojdbc8.jar (for Java 8 and 11) from the 19c Oracle Home into Tomcat's lib folder. The datasource is created by Tomcat itself, so the driver must be in Tomcat's classpath, not inside the WAR.
scp oracle@dbhost:/u01/app/oracle/product/19.0.0/dbhome_1/jdbc/lib/ojdbc8.jar /tmp/ sudo -u tomcat cp /tmp/ojdbc8.jar $CATALINA_HOME/lib/
STEP 5
Configure the Oracle datasource
There are two places to define a JNDI datasource in Tomcat:
| Where | Scope | When to use |
|---|---|---|
conf/context.xml | Shared by every application on this Tomcat | Simplest: one app, or several apps on the same schema |
conf/server.xml <GlobalNamingResources> | Global; each app must add a <ResourceLink> | Many apps that must share one pool |
For most setups, context.xml is the simpler choice. Add this inside <Context>:
<Context>
...
<Resource name="jdbc/AppDS"
auth="Container"
type="javax.sql.DataSource"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
driverClassName="oracle.jdbc.OracleDriver"
url="jdbc:oracle:thin:@//dbhost:1521/ORCLPDB1"
username="app_user"
password="App#Pass2026"
initialSize="10"
minIdle="10"
maxIdle="50"
maxActive="100"
maxWait="10000"
validationQuery="SELECT 1 FROM DUAL"
testOnBorrow="true"
validationInterval="30000"
removeAbandoned="true"
removeAbandonedTimeout="300"
logAbandoned="true"
timeBetweenEvictionRunsMillis="30000"
minEvictableIdleTimeMillis="60000"
defaultAutoCommit="true" />
</Context>
The application looks the pool up as java:comp/env/jdbc/AppDS. Confirm the exact name with the development team.
@//host:port/SERVICE_NAME is the modern form and works with pluggable databases. The old @host:port:SID form cannot connect to a PDB.Understanding the pool settings
These are sensible starting values for a medium application, not magic numbers. Each card explains what the setting controls.
PROCESSES and SESSIONS first:SQL> SELECT resource_name, current_utilization, max_utilization, limit_value
FROM v$resource_limit
WHERE resource_name IN ('processes','sessions');
SELECT 1 FROM DUAL touches nothing.STEP 6
Start Tomcat and check the log
sudo -u tomcat $CATALINA_HOME/bin/startup.sh tail -f $CATALINA_HOME/logs/catalina.out
INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"] INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [2,481] milliseconds
Then open http://<server-ip>:8080/. You should see the Tomcat welcome page. If not, check the firewall: sudo firewall-cmd --add-port=8080/tcp --permanent && sudo firewall-cmd --reload.
<Connector port="8080" protocol="HTTP/1.1"> line in conf/server.xml. Change it there if 8080 is taken.STEP 7
Deploy the WAR
With the default autoDeploy="true" on the <Host>, copying the WAR into webapps is all it takes. Tomcat unpacks and starts it within seconds.
scp sample-app.war tomcat@appserver:/tmp/ # from your machine (or WinSCP) sudo -u tomcat cp /tmp/sample-app.war $CATALINA_HOME/webapps/ ls -l $CATALINA_HOME/webapps/ grep -E "Deploying web application archive|has finished|SEVERE" $CATALINA_HOME/logs/catalina.out | tail
The WAR file name becomes the URL path: sample-app.war is served at http://<server-ip>:8080/sample-app/. Rename it to ROOT.war to serve it at the root path.
curl -I http://localhost:8080/sample-app/
STEP 8
Run Tomcat as a systemd service
So that Tomcat starts at boot and can be managed like any other service:
[Unit] Description=Apache Tomcat 9 After=network.target [Service] Type=forking User=tomcat Group=tomcat Environment=CATALINA_HOME=/opt/tomcat/latest Environment=CATALINA_BASE=/opt/tomcat/latest ExecStart=/opt/tomcat/latest/bin/startup.sh ExecStop=/opt/tomcat/latest/bin/shutdown.sh PIDFile=/opt/tomcat/latest/temp/tomcat.pid Restart=on-failure [Install] WantedBy=multi-user.target
sudo systemctl daemon-reload sudo systemctl enable --now tomcat sudo systemctl status tomcat
STEP 9
Stop Tomcat
sudo systemctl stop tomcat # or, without systemd sudo -u tomcat $CATALINA_HOME/bin/shutdown.sh
Troubleshooting
| Message in catalina.out | Cause | Fix |
|---|---|---|
| ClassNotFoundException: oracle.jdbc.OracleDriver | ojdbc8.jar is not in $CATALINA_HOME/lib | Copy it there and restart Tomcat |
| Name [jdbc/AppDS] is not bound in this Context | JNDI name mismatch, or a global resource without a ResourceLink | Match the name the app uses; add a ResourceLink if you used server.xml |
| ORA-12514 / IO Error: Network Adapter | Wrong service name, host or port, or a firewall | Test with nc -zv dbhost 1521 and sqlplus app_user@//dbhost:1521/ORCLPDB1 |
| Timeout: Pool empty. Unable to fetch a connection in 10 seconds | All maxActive connections are busy, or leaking | Look for logAbandoned stack traces; check slow SQL before raising maxActive |
| ORA-00020 maximum number of processes exceeded | Pools across all instances exceed the DB limit | Lower maxActive, or raise PROCESSES on the database |
| Address already in use on startup | Port 8080 (or 8005 shutdown, 8009 AJP) is taken | ss -tlnp | grep 8080; change the Connector port |
Conclusion
Tomcat itself installs in minutes. The part that decides whether the application runs smoothly is the datasource. Use a service-name URL, keep the driver in lib, validate with SELECT 1 FROM DUAL, turn on leak detection, and size maxActive together with the database's PROCESSES limit, not in isolation.
What pool settings do you use in production? Share them in the comments.
Tamilselvan Perumal · Lead Product Engineer & Senior DBA
