Integrate Apache Tomcat 11 with Prometheus and Grafana monitoring for comprehensive performance tracking

Intermediate 45 min Apr 28, 2026 64 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up comprehensive monitoring for Apache Tomcat 11 using JMX Prometheus Java agent, configure Prometheus to scrape metrics, create detailed Grafana dashboards, and implement alerting rules for performance tracking and health monitoring.

Prerequisites

  • Root or sudo access
  • At least 4GB RAM
  • Java 11 or higher

What this solves

Apache Tomcat serves millions of Java applications but lacks built-in monitoring dashboards. This tutorial integrates Tomcat 11 with Prometheus and Grafana to provide real-time visibility into JVM memory usage, thread pools, request throughput, and application health. You'll get production-ready dashboards and alerts that help you identify performance bottlenecks before they impact users.

Step-by-step configuration

Install required packages

Update your system and install Java 11 or higher, which is required for Tomcat 11.

sudo apt update
sudo apt install -y openjdk-11-jdk wget curl
sudo dnf update -y
sudo dnf install -y java-11-openjdk-devel wget curl

Download and install Apache Tomcat 11

Create a dedicated tomcat user and download the latest Tomcat 11 release.

sudo useradd -r -m -U -d /opt/tomcat -s /bin/false tomcat
cd /tmp
wget https://archive.apache.org/dist/tomcat/tomcat-11/v11.0.0/bin/apache-tomcat-11.0.0.tar.gz
sudo tar xf apache-tomcat-11.0.0.tar.gz -C /opt/tomcat --strip-components=1
sudo chown -R tomcat:tomcat /opt/tomcat
sudo chmod +x /opt/tomcat/bin/*.sh

Download JMX Prometheus Java agent

The JMX Prometheus Java agent exports JVM metrics in Prometheus format via HTTP endpoint.

cd /opt/tomcat/lib
sudo wget https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.20.0/jmx_prometheus_javaagent-0.20.0.jar
sudo chown tomcat:tomcat jmx_prometheus_javaagent-0.20.0.jar

Create JMX exporter configuration

Configure which JMX metrics to export and how to transform them for Prometheus.

rules:
  # JVM Memory metrics
  - pattern: 'java.lang<>HeapMemoryUsage'
    name: jvm_memory_heap_bytes
    type: GAUGE
    labels:
      area: "heap"
    help: "Heap memory usage in bytes"
    
  - pattern: 'java.lang<>NonHeapMemoryUsage'
    name: jvm_memory_nonheap_bytes
    type: GAUGE
    labels:
      area: "nonheap"
    help: "Non-heap memory usage in bytes"
    
  # Thread metrics
  - pattern: 'java.lang<>ThreadCount'
    name: jvm_threads_current
    type: GAUGE
    help: "Current thread count"
    
  - pattern: 'java.lang<>PeakThreadCount'
    name: jvm_threads_peak
    type: GAUGE
    help: "Peak thread count"
    
  # Tomcat connector metrics
  - pattern: 'Catalina<>currentThreadCount'
    name: tomcat_threads_current
    type: GAUGE
    labels:
      connector: "$1"
    help: "Current thread count for connector"
    
  - pattern: 'Catalina<>currentThreadsBusy'
    name: tomcat_threads_busy
    type: GAUGE
    labels:
      connector: "$1"
    help: "Current busy thread count for connector"
    
  - pattern: 'Catalina<>maxThreads'
    name: tomcat_threads_max
    type: GAUGE
    labels:
      connector: "$1"
    help: "Maximum thread count for connector"
    
  # Request metrics
  - pattern: 'Catalina<>requestCount'
    name: tomcat_requests_total
    type: COUNTER
    labels:
      connector: "$1"
    help: "Total request count"
    
  - pattern: 'Catalina<>processingTime'
    name: tomcat_processing_time_milliseconds_total
    type: COUNTER
    labels:
      connector: "$1"
    help: "Total processing time in milliseconds"
    
  - pattern: 'Catalina<>bytesReceived'
    name: tomcat_bytes_received_total
    type: COUNTER
    labels:
      connector: "$1"
    help: "Total bytes received"
    
  - pattern: 'Catalina<>bytesSent'
    name: tomcat_bytes_sent_total
    type: COUNTER
    labels:
      connector: "$1"
    help: "Total bytes sent"
    
  # Session metrics
  - pattern: 'Catalina<>activeSessions'
    name: tomcat_sessions_active
    type: GAUGE
    labels:
      host: "$1"
      context: "$2"
    help: "Active session count"
    
  - pattern: 'Catalina<>sessionCounter'
    name: tomcat_sessions_created_total
    type: COUNTER
    labels:
      host: "$1"
      context: "$2"
    help: "Total sessions created"
    
  # Garbage Collection metrics
  - pattern: 'java.lang<>CollectionCount'
    name: jvm_gc_collections_total
    type: COUNTER
    labels:
      gc: "$1"
    help: "Total garbage collections"
    
  - pattern: 'java.lang<>CollectionTime'
    name: jvm_gc_collection_time_milliseconds_total
    type: COUNTER
    labels:
      gc: "$1"
    help: "Total garbage collection time in milliseconds"
sudo chown tomcat:tomcat /opt/tomcat/conf/jmx-exporter-config.yml

Configure Tomcat to use JMX exporter

Add the JMX Prometheus agent to Tomcat's JVM startup options.

#!/bin/bash
CATALINA_OPTS="$CATALINA_OPTS -javaagent:/opt/tomcat/lib/jmx_prometheus_javaagent-0.20.0.jar=8088:/opt/tomcat/conf/jmx-exporter-config.yml"
JAVA_OPTS="$JAVA_OPTS -Xmx2048m -Xms1024m -XX:+UseG1GC"
sudo chown tomcat:tomcat /opt/tomcat/bin/setenv.sh
sudo chmod +x /opt/tomcat/bin/setenv.sh

Create systemd service for Tomcat

Configure Tomcat to start automatically and run as the tomcat user.

[Unit]
Description=Apache Tomcat Web Application Container
After=network.target

[Service]
Type=forking
User=tomcat
Group=tomcat
Environment=JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid
Environment=CATALINA_HOME=/opt/tomcat
Environment=CATALINA_BASE=/opt/tomcat
Environment="CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC"
Environment="JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom"
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
RestartSec=10
Restart=always

[Install]
WantedBy=multi-user.target

Start Tomcat and verify JMX exporter

Enable and start the Tomcat service, then verify the metrics endpoint is working.

sudo systemctl daemon-reload
sudo systemctl enable --now tomcat
sudo systemctl status tomcat
curl http://localhost:8088/metrics | head -20
Note: If you're running this on a remote server, you may need to configure your firewall to allow access to port 8088 for Prometheus scraping.

Install Prometheus

Download and install Prometheus to scrape metrics from Tomcat.

sudo useradd -r -M -s /bin/false prometheus
sudo mkdir -p /etc/prometheus /var/lib/prometheus
cd /tmp
wget https://github.com/prometheus/prometheus/releases/download/v2.48.0/prometheus-2.48.0.linux-amd64.tar.gz
tar xf prometheus-2.48.0.linux-amd64.tar.gz
sudo cp prometheus-2.48.0.linux-amd64/prometheus /usr/local/bin/
sudo cp prometheus-2.48.0.linux-amd64/promtool /usr/local/bin/
sudo chown prometheus:prometheus /usr/local/bin/prometheus /usr/local/bin/promtool
sudo chown -R prometheus:prometheus /etc/prometheus /var/lib/prometheus

Configure Prometheus to scrape Tomcat metrics

Set up Prometheus configuration to collect metrics from the Tomcat JMX exporter.

global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "tomcat_alerts.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets: []

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']
  
  - job_name: 'tomcat'
    static_configs:
      - targets: ['localhost:8088']
    scrape_interval: 5s
    metrics_path: '/metrics'
    scrape_timeout: 5s
sudo chown prometheus:prometheus /etc/prometheus/prometheus.yml

Create Prometheus alerting rules

Define alert conditions for Tomcat performance and health monitoring.

groups:
  - name: tomcat_alerts
    rules:
      - alert: TomcatDown
        expr: up{job="tomcat"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Tomcat instance is down"
          description: "Tomcat metrics endpoint has been unreachable for more than 1 minute."
          
      - alert: TomcatHighMemoryUsage
        expr: (jvm_memory_heap_bytes{area="heap"} / jvm_memory_heap_max_bytes{area="heap"}) * 100 > 85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Tomcat high heap memory usage"
          description: "Tomcat heap memory usage is above 85% for more than 5 minutes. Current usage: {{ $value }}%"
          
      - alert: TomcatHighThreadUsage
        expr: (tomcat_threads_busy / tomcat_threads_max) * 100 > 80
        for: 3m
        labels:
          severity: warning
        annotations:
          summary: "Tomcat high thread pool usage"
          description: "Tomcat thread pool usage is above 80% for connector {{ $labels.connector }}. Current usage: {{ $value }}%"
          
      - alert: TomcatHighResponseTime
        expr: rate(tomcat_processing_time_milliseconds_total[5m]) / rate(tomcat_requests_total[5m]) > 5000
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "Tomcat high response time"
          description: "Average response time for connector {{ $labels.connector }} is above 5 seconds: {{ $value }}ms"
          
      - alert: TomcatHighGCTime
        expr: rate(jvm_gc_collection_time_milliseconds_total[5m]) > 100
        for: 3m
        labels:
          severity: warning
        annotations:
          summary: "Tomcat high garbage collection time"
          description: "Garbage collection time rate is high for collector {{ $labels.gc }}: {{ $value }}ms/sec"
          
      - alert: TomcatNoActiveThreads
        expr: tomcat_threads_current - tomcat_threads_busy < 5
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Tomcat low available threads"
          description: "Less than 5 threads available for connector {{ $labels.connector }}"
          
      - alert: TomcatHighErrorRate
        expr: rate(tomcat_servlet_errors_total[5m]) / rate(tomcat_requests_total[5m]) * 100 > 5
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "Tomcat high error rate"
          description: "Error rate is above 5% for the last 5 minutes: {{ $value }}%"
sudo chown prometheus:prometheus /etc/prometheus/tomcat_alerts.yml

Create Prometheus systemd service

Configure Prometheus to start automatically on system boot.

[Unit]
Description=Prometheus
Wants=network-online.target
After=network-online.target

[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
  --config.file /etc/prometheus/prometheus.yml \
  --storage.tsdb.path /var/lib/prometheus/ \
  --web.console.templates=/etc/prometheus/consoles \
  --web.console.libraries=/etc/prometheus/console_libraries \
  --web.listen-address=0.0.0.0:9090 \
  --web.enable-lifecycle
Restart=always

[Install]
WantedBy=multi-user.target

Start Prometheus service

Enable and start Prometheus, then verify it's collecting Tomcat metrics.

sudo systemctl daemon-reload
sudo systemctl enable --now prometheus
sudo systemctl status prometheus

Install Grafana

Add Grafana repository and install the latest version for dashboard visualization.

sudo apt install -y software-properties-common
wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update
sudo apt install -y grafana
sudo tee /etc/yum.repos.d/grafana.repo <

Configure Grafana datasource

Add Prometheus as a data source for Grafana dashboards.

apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://localhost:9090
    isDefault: true
    editable: true

Create Tomcat dashboard configuration

Set up a comprehensive Grafana dashboard for Tomcat monitoring.

{
  "dashboard": {
    "id": null,
    "title": "Apache Tomcat Performance Dashboard",
    "tags": ["tomcat", "jvm", "performance"],
    "timezone": "browser",
    "panels": [
      {
        "id": 1,
        "title": "Tomcat Status",
        "type": "stat",
        "targets": [
          {
            "expr": "up{job=\"tomcat\"}",
            "legendFormat": "Status"
          }
        ],
        "fieldConfig": {
          "defaults": {
            "mappings": [
              {
                "options": {
                  "0": {"text": "DOWN", "color": "red"},
                  "1": {"text": "UP", "color": "green"}
                },
                "type": "value"
              }
            ],
            "thresholds": {
              "steps": [
                {"color": "red", "value": 0},
                {"color": "green", "value": 1}
              ]
            }
          }
        },
        "gridPos": {"h": 8, "w": 12, "x": 0, "y": 0}
      },
      {
        "id": 2,
        "title": "Heap Memory Usage",
        "type": "timeseries",
        "targets": [
          {
            "expr": "jvm_memory_heap_bytes{area=\"heap\"}",
            "legendFormat": "Used Heap Memory"
          },
          {
            "expr": "jvm_memory_heap_max_bytes{area=\"heap\"}",
            "legendFormat": "Max Heap Memory"
          }
        ],
        "fieldConfig": {
          "defaults": {
            "unit": "bytes",
            "custom": {
              "drawStyle": "line",
              "lineInterpolation": "linear",
              "barAlignment": 0,
              "lineWidth": 1,
              "fillOpacity": 10,
              "gradientMode": "none",
              "spanNulls": false,
              "insertNulls": false,
              "showPoints": "never",
              "pointSize": 5,
              "stacking": {"mode": "none", "group": "A"},
              "axisPlacement": "auto",
              "axisLabel": "",
              "axisColorMode": "text",
              "scaleDistribution": {"type": "linear"},
              "axisCenteredZero": false,
              "hideFrom": {"legend": false, "tooltip": false, "vis": false},
              "thresholdsStyle": {"mode": "off"}
            }
          }
        },
        "gridPos": {"h": 8, "w": 12, "x": 12, "y": 0}
      },
      {
        "id": 3,
        "title": "Thread Pool Usage",
        "type": "timeseries",
        "targets": [
          {
            "expr": "tomcat_threads_current",
            "legendFormat": "Current Threads - {{connector}}"
          },
          {
            "expr": "tomcat_threads_busy",
            "legendFormat": "Busy Threads - {{connector}}"
          },
          {
            "expr": "tomcat_threads_max",
            "legendFormat": "Max Threads - {{connector}}"
          }
        ],
        "gridPos": {"h": 8, "w": 12, "x": 0, "y": 8}
      },
      {
        "id": 4,
        "title": "Request Rate",
        "type": "timeseries",
        "targets": [
          {
            "expr": "rate(tomcat_requests_total[5m])",
            "legendFormat": "Requests/sec - {{connector}}"
          }
        ],
        "fieldConfig": {
          "defaults": {
            "unit": "reqps"
          }
        },
        "gridPos": {"h": 8, "w": 12, "x": 12, "y": 8}
      },
      {
        "id": 5,
        "title": "Average Response Time",
        "type": "timeseries",
        "targets": [
          {
            "expr": "rate(tomcat_processing_time_milliseconds_total[5m]) / rate(tomcat_requests_total[5m])",
            "legendFormat": "Avg Response Time - {{connector}}"
          }
        ],
        "fieldConfig": {
          "defaults": {
            "unit": "ms"
          }
        },
        "gridPos": {"h": 8, "w": 12, "x": 0, "y": 16}
      },
      {
        "id": 6,
        "title": "GC Activity",
        "type": "timeseries",
        "targets": [
          {
            "expr": "rate(jvm_gc_collection_time_milliseconds_total[5m])",
            "legendFormat": "GC Time/sec - {{gc}}"
          }
        ],
        "fieldConfig": {
          "defaults": {
            "unit": "ms"
          }
        },
        "gridPos": {"h": 8, "w": 12, "x": 12, "y": 16}
      }
    ],
    "time": {
      "from": "now-1h",
      "to": "now"
    },
    "refresh": "5s",
    "version": 1
  }
}

Enable dashboard provisioning

Configure Grafana to automatically load the Tomcat dashboard.

apiVersion: 1

providers:
  - name: 'tomcat-dashboards'
    orgId: 1
    folder: 'Tomcat'
    type: file
    disableDeletion: false
    updateIntervalSeconds: 10
    allowUiUpdates: true
    options:
      path: /etc/grafana/provisioning/dashboards

Start Grafana service

Enable and start Grafana to access the monitoring dashboard.

sudo systemctl enable --now grafana-server
sudo systemctl status grafana-server

Configure firewall rules

Allow access to Grafana, Prometheus, and Tomcat metrics endpoints.

sudo ufw allow 3000/tcp comment "Grafana"
sudo ufw allow 9090/tcp comment "Prometheus"
sudo ufw allow 8080/tcp comment "Tomcat"
sudo ufw allow 8088/tcp comment "Tomcat Metrics"
sudo firewall-cmd --permanent --add-port=3000/tcp
sudo firewall-cmd --permanent --add-port=9090/tcp
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --permanent --add-port=8088/tcp
sudo firewall-cmd --reload

Verify your setup

Test that all components are working correctly and collecting metrics.

# Check Tomcat is running and metrics are available
sudo systemctl status tomcat
curl -s http://localhost:8088/metrics | grep "jvm_memory_heap_bytes" | head -5

Check Prometheus is scraping Tomcat

sudo systemctl status prometheus curl -s "http://localhost:9090/api/v1/query?query=up{job='tomcat'}" | jq '.data.result[0].value[1]'

Check Grafana is running

sudo systemctl status grafana-server curl -s http://localhost:3000/api/health

Access your monitoring setup:

  • Grafana dashboard: http://your-server-ip:3000 (admin/admin)
  • Prometheus interface: http://your-server-ip:9090
  • Tomcat web interface: http://your-server-ip:8080
  • Tomcat metrics endpoint: http://your-server-ip:8088/metrics

Configure advanced monitoring features

Add custom JVM metrics

Extend monitoring to include additional JVM performance indicators.

# Add these additional rules to your existing configuration
  # Class loading metrics
  - pattern: 'java.lang<>LoadedClassCount'
    name: jvm_classes_loaded
    type: GAUGE
    help: "Number of classes currently loaded"
    
  - pattern: 'java.lang<>TotalLoadedClassCount'
    name: jvm_classes_loaded_total
    type: COUNTER
    help: "Total number of classes loaded since JVM start"
    
  # Operating System metrics
  - pattern: 'java.lang<>SystemCpuLoad'
    name: system_cpu_load
    type: GAUGE
    help: "System CPU load"
    
  - pattern: 'java.lang<>ProcessCpuLoad'
    name: process_cpu_load
    type: GAUGE
    help: "Process CPU load"
    
  # Connection pool metrics (if using JNDI datasources)
  - pattern: 'Catalina<>numActive'
    name: tomcat_datasource_connections_active
    type: GAUGE
    labels:
      host: "$1"
      context: "$2"
      datasource: "$3"
    help: "Active database connections"
    
  - pattern: 'Catalina<>maxActive'
    name: tomcat_datasource_connections_max
    type: GAUGE
    labels:
      host: "$1"
      context: "$2"
      datasource: "$3"
    help: "Maximum database connections"

Create application-specific dashboards

Set up monitoring for specific web applications deployed to Tomcat.

{
  "dashboard": {
    "title": "Tomcat Applications Dashboard",
    "panels": [
      {
        "id": 10,
        "title": "Active Sessions by Application",
        "type": "timeseries",
        "targets": [
          {
            "expr": "tomcat_sessions_active",
            "legendFormat": "{{host}}/{{context}}"
          }
        ],
        "gridPos": {"h": 8, "w": 12, "x": 0, "y": 0}
      },
      {
        "id": 11,
        "title": "Database Connection Usage",
        "type": "timeseries",
        "targets": [
          {
            "expr": "tomcat_datasource_connections_active",
            "legendFormat": "Active - {{datasource}}"
          },
          {
            "expr": "tomcat_datasource_connections_max",
            "legendFormat": "Max - {{datasource}}"
          }
        ],
        "gridPos": {"h": 8, "w": 12, "x": 12, "y": 0}
      }
    ],
    "time": {"from": "now-1h", "to": "now"},
    "refresh": "30s"
  }
}

Set up alert notification channels

Configure Grafana to send alerts via email or Slack when thresholds are exceeded.

notifiers:
  - name: email-alerts
    type: email
    uid: email001
    org_id: 1
    is_default: true
    settings:
      addresses: admin@example.com
      subject: "Grafana Alert - {{ .CommonLabels.alertname }}"
    secure_settings:
      password: your_email_password

Similar to our MySQL monitoring setup, you can expand this configuration to monitor additional application metrics and set up more sophisticated alerting rules.

Performance tuning recommendations

Optimize Prometheus retention and storage

Configure data retention policies to balance storage usage with monitoring history.

# Update the ExecStart line to include retention settings
ExecStart=/usr/local/bin/prometheus \
  --config.file /etc/prometheus/prometheus.yml \
  --storage.tsdb.path /var/lib/prometheus/ \
  --web.console.templates=/etc/prometheus/consoles \
  --web.console.libraries=/etc/prometheus/console_libraries \
  --web.listen-address=0.0.0.0:9090 \
  --web.enable-lifecycle \
  --storage.tsdb.retention.time=30d \
  --storage.tsdb.retention.size=10GB
sudo systemctl daemon-reload
sudo systemctl restart prometheus

Configure log rotation for metrics

Set up log rotation to prevent disk space issues with monitoring logs.

/opt/tomcat/logs/*.log {
    weekly
    missingok
    rotate 4
    compress
    notifempty
    create 644 tomcat tomcat
    postrotate
        systemctl reload tomcat
    endscript
}

/var/log/grafana/*.log {
    weekly
    missingok
    rotate 4
    compress
    notifempty
    create 644 grafana grafana
}
Never use chmod 777. It gives every user on the system full access to your files. Instead, fix ownership with chown and use minimal permissions like 644 for config files and 755 for directories.

Common issues

Symptom Cause Fix
JMX metrics not appearing JMX agent not loaded or wrong port curl http://localhost:8088/metrics and check /opt/tomcat/logs/catalina.out
Prometheus shows target down Firewall blocking port 8088 Configure firewall rules or check network connectivity
Grafana dashboard empty Wrong datasource URL or no data Verify Prometheus URL in datasource config and check metrics availability
High memory usage Too frequent scraping or retention too long Increase scrape intervals or reduce retention time in Prometheus config
Tomcat slow startup JMX agent adding overhead Increase JVM memory allocation or optimize JMX exporter rules
Missing thread pool metrics Connector names changed in Tomcat config Update JMX exporter pattern matching in configuration file

Next steps

Running this in production?

Want this handled for you? Setting up monitoring once is straightforward. Keeping it tuned, managing retention policies, handling alert fatigue, and maintaining dashboards across environments is the harder part. See how we run infrastructure like this for European SaaS and e-commerce teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed devops services for businesses that depend on uptime. From initial setup to ongoing operations.