tutorial

How to Monitor Apache MyFaces JSF Application Health with Vigilmon

Apache MyFaces ViewState failures, managed bean lifecycle errors, and EJB timer crashes are silent until users report broken forms or missing page state. Learn how to monitor MyFaces application health, JSF lifecycle availability, and background job liveness with Vigilmon HTTP probes and heartbeat monitors.

Apache MyFaces is a production-grade JavaServer Faces (JSF) implementation that powers enterprise Java EE and Jakarta EE web applications through a stateful component lifecycle, managed bean dependency injection, and server-side view state management. MyFaces sits in the critical path of every page render and form postback — when the session store is near capacity, ViewExpiredException is thrown on every postback; when a phase listener throws during the INVOKE_APPLICATION phase, JSF catches the exception and renders a styled error page that returns HTTP 200; when the EL resolver fails to find a managed bean property, the form field renders blank with no visible error. These are production incidents disguised as UI rendering problems.

Vigilmon gives you external visibility into MyFaces's JSF layer through HTTP probe monitoring and heartbeat monitors. This tutorial covers both.


Why MyFaces Needs External Monitoring

MyFaces's failure modes are subtle and high-impact:

  • Phase listener exceptions returning 200: JSF's ExceptionHandlerFactory catches unhandled exceptions in RESTORE_VIEW, UPDATE_MODEL_VALUES, and INVOKE_APPLICATION and renders a styled error page that returns HTTP 200 — load balancers and simple ping monitors see a healthy response while users see broken pages
  • ViewState and session failures: MyFaces stores view state server-side in the HTTP session; when the session store is full, replicated incorrectly, or corrupted, every form postback throws ViewExpiredException — in default configuration this returns 200 and is invisible to standard uptime monitors
  • CDI and managed bean scope failures: CDI @SessionScoped or @ConversationScoped beans that fail to deserialize after a session store failover throw ContextNotActiveException on the first request that touches them, affecting all users whose sessions crossed the failover boundary
  • EL expression resolution failures: when the EL resolver cannot find a managed bean property (due to a refactoring that missed a .xhtml template), the affected form field renders blank with no exception thrown — the page appears to work while data is silently lost
  • Partial state saving failures: MyFaces's partial state saving (javax.faces.PARTIAL_STATE_SAVING) can produce inconsistent component trees after a cluster node failover, causing subsequent requests to throw ClassCastException or IllegalStateException during view restoration
  • EJB @Schedule timer silent failures: EJB timer methods that fail during execution are rescheduled by the container without any HTTP-visible signal — data pipelines stop running while the application appears healthy

External monitoring with Vigilmon adds:

  • Proactive alerting when JSF health endpoints detect phase listener or session store failures
  • ViewState and session availability monitoring independent of the application server
  • Heartbeat monitoring so you know when @Schedule EJB timers stop completing successfully
  • Multi-region probe consensus that filters transient blips from genuine JSF application failures

Step 1: Build a MyFaces Health Endpoint

MyFaces does not ship with a built-in health endpoint. Add one as a JAX-RS resource that bypasses the JSF lifecycle entirely, or via Spring Boot Actuator in hybrid setups.

JAX-RS Health Resource (bypasses JSF lifecycle)

// health/HealthResource.java
package com.yourapp.health;

import com.yourapp.service.DatabaseService;
import com.yourapp.service.CacheService;
import org.apache.myfaces.shared.config.MyfacesConfig;
import javax.inject.Inject;
import javax.servlet.ServletContext;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.util.*;

@Path("/health")
public class HealthResource {

    @Inject
    private DatabaseService databaseService;

    @Inject
    private CacheService cacheService;

    @Context
    private ServletContext servletContext;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response check() {
        Map<String, String> checks = new LinkedHashMap<>();
        boolean anyFailed = false;

        checks.put("database", databaseService.isHealthy() ? "ok" : "down");
        checks.put("cache",    cacheService.isHealthy()    ? "ok" : "down");
        checks.put("myfaces",  isMyfacesInitialized()      ? "ok" : "down");

        for (String v : checks.values()) {
            if ("down".equals(v)) { anyFailed = true; break; }
        }

        Map<String, Object> response = new LinkedHashMap<>();
        response.put("status", anyFailed ? "degraded" : "ok");
        response.put("checks", checks);

        return Response.status(anyFailed ? 503 : 200).entity(response).build();
    }

    private boolean isMyfacesInitialized() {
        try {
            MyfacesConfig config = MyfacesConfig.getCurrentInstance(servletContext);
            return config != null;
        } catch (Exception e) {
            return false;
        }
    }
}

Register the JAX-RS application in web.xml or via annotation:

@ApplicationPath("/api")
public class RestApplication extends Application {}

Your health endpoint is now at /api/health.

Spring Boot Actuator Health Indicator (MyFaces + Spring Boot)

// MyfacesHealthIndicator.java
import org.apache.myfaces.shared.config.MyfacesConfig;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import javax.servlet.ServletContext;

@Component
public class MyfacesHealthIndicator implements HealthIndicator {

    private final ServletContext servletContext;

    public MyfacesHealthIndicator(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    @Override
    public Health health() {
        try {
            MyfacesConfig config = MyfacesConfig.getCurrentInstance(servletContext);
            if (config == null) {
                return Health.down()
                    .withDetail("reason", "myfaces_config_null")
                    .build();
            }

            return Health.up()
                .withDetail("partial_state_saving", config.isPartialStateSaving())
                .withDetail("client_side_state", config.isClientSideStateCache())
                .build();
        } catch (Exception e) {
            return Health.down()
                .withDetail("reason", "myfaces_not_initialized")
                .withDetail("error", e.getMessage())
                .build();
        }
    }
}

This exposes /actuator/health/myfaces automatically via Spring Boot Actuator.

Node.js Sidecar

// health/myfaces.js
const express = require('express');
const http = require('http');

const app = express();
const APP_HOST = process.env.APP_HOST || 'localhost';
const APP_PORT = parseInt(process.env.APP_PORT || '8080');
const HEALTH_PATH = process.env.MYFACES_HEALTH_PATH || '/api/health';

function probeMyFaces() {
  return new Promise((resolve, reject) => {
    const req = http.get(
      { host: APP_HOST, port: APP_PORT, path: HEALTH_PATH, timeout: 5000 },
      res => {
        let body = '';
        res.on('data', chunk => { body += chunk; });
        res.on('end', () => resolve({ status: res.statusCode, body }));
      }
    );
    req.on('error', reject);
    req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
  });
}

app.get('/health/myfaces', async (req, res) => {
  try {
    const result = await probeMyFaces();
    const up = result.status === 200 &&
      (result.body.includes('"status":"ok"') || result.body.includes('"status":"UP"'));

    if (!up) {
      return res.status(503).json({
        status: 'down',
        upstream_status: result.status,
        body: result.body.substring(0, 500),
      });
    }

    return res.status(200).json({ status: 'ok' });
  } catch (err) {
    return res.status(503).json({ status: 'down', error: err.message });
  }
});

app.listen(3015);

Python Health Sidecar

# health_myfaces.py
import os, requests
from flask import Flask, jsonify

app = Flask(__name__)

APP_BASE = f"http://{os.environ.get('APP_HOST', 'localhost')}:{os.environ.get('APP_PORT', '8080')}"
HEALTH_PATH = os.environ.get('MYFACES_HEALTH_PATH', '/api/health')

@app.route('/health/myfaces')
def health():
    try:
        resp = requests.get(f'{APP_BASE}{HEALTH_PATH}', timeout=5)
        if resp.status_code != 200:
            return jsonify({'status': 'down', 'upstream_status': resp.status_code}), 503

        data = resp.json()
        if data.get('status') not in ('ok', 'UP'):
            return jsonify({'status': 'degraded', 'detail': data}), 503

        return jsonify({'status': 'ok', 'checks': data.get('checks', {})})
    except Exception as e:
        return jsonify({'status': 'down', 'error': str(e)}), 503

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3015)

Step 2: Configure Vigilmon HTTP Monitor for MyFaces

  1. Log in to vigilmon.online and go to Monitors → New Monitor
  2. Choose HTTP / HTTPS
  3. Set the URL to your MyFaces health endpoint: https://your-app.example.com/api/health
  4. Set the check interval to 1 minute
  5. Under Expected response, configure:
    • Status code: 200
    • Response body contains: "status":"ok" or "status":"UP"
    • Response time threshold: 3000ms
  6. Under Alert channels, assign your Slack or PagerDuty channel
  7. Save the monitor

Add a second monitor for the MyFaces initialization check:

  • URL: https://your-app.example.com/actuator/health/myfaces
  • Expected: 200, body contains: "status":"UP"
  • Interval: 2 minutes
  • Alert channel: P1 pager — MyFaces initialization failure means all JSF pages are broken

Vigilmon's multi-region probe consensus filters transient network blips from genuine MyFaces application failures.


Step 3: Heartbeat Monitoring for EJB Timer Completion

Health endpoint monitoring catches JSF initialization and session store failures, but not background timer execution problems. A MyFaces application can report healthy while an EJB @Schedule timer has been silently failing every run due to a CDI scope boundary issue or a database transaction rollback.

Vigilmon heartbeat monitors catch these: your EJB timer pings Vigilmon after each successful execution.

Set Up the Heartbeat Monitor

  1. In Vigilmon, go to Monitors → New Monitor → Heartbeat
  2. Set the name: myfaces-ejb-timer
  3. Set the expected interval: 25 hours (for a daily job)
  4. Set the grace period: 2 hours
  5. Save — copy the heartbeat URL, e.g. https://vigilmon.online/heartbeat/abc123xyz

Wire It Into an EJB @Schedule Timer

// timer/DailyReportTimer.java
package com.yourapp.timer;

import javax.ejb.*;
import java.net.http.*;
import java.net.URI;
import java.util.logging.Logger;

@Singleton
@Startup
public class DailyReportTimer {

    private static final Logger LOG =
        Logger.getLogger(DailyReportTimer.class.getName());

    private final HttpClient http = HttpClient.newHttpClient();
    private final String heartbeatUrl = System.getProperty("VIGILMON_HEARTBEAT_URL", "");

    @Schedule(hour = "2", minute = "0", second = "0", persistent = false)
    public void generateDailyReport() {
        try {
            runReport();
            pingHeartbeat();
        } catch (Exception e) {
            LOG.severe("Daily report failed: " + e.getMessage());
            // Heartbeat not pinged — Vigilmon alerts on the missed heartbeat
        }
    }

    private void runReport() throws Exception {
        // report generation logic
    }

    private void pingHeartbeat() {
        if (heartbeatUrl.isEmpty()) return;
        try {
            http.sendAsync(
                HttpRequest.newBuilder(URI.create(heartbeatUrl)).GET().build(),
                HttpResponse.BodyHandlers.discarding()
            );
        } catch (Exception ignored) {}
    }
}

Pass the heartbeat URL as a JVM system property to your application server:

-DVIGILMON_HEARTBEAT_URL=https://vigilmon.online/heartbeat/abc123xyz

Step 4: Alert Routing for MyFaces Failures

MyFaces failures range from a single managed bean scope failing to the entire JSF runtime being uninitialized after a deployment. Route alerts by severity:

| Monitor | Alert Channel | Priority | |---|---|---| | HTTP: /api/health JSF dependency check | Slack + PagerDuty | P1 | | HTTP: /actuator/health/myfaces | Slack + PagerDuty | P1 | | Heartbeat: EJB daily report timer | Slack + PagerDuty | P1 | | Heartbeat: conversation cleanup job | Slack + email | P2 |

Set response time thresholds as early warning signals:

  • Alert at 800ms for the MyFaces health endpoint (slow responses indicate session store or database latency)
  • Alert at 2000ms for the JSF initialization check (slow initialization precedes full page render failures)
  • Alert at 2 hours past the expected daily timer heartbeat window

For production applications, set up a status page in Vigilmon showing JSF application health and EJB timer liveness — this lets on-call engineers identify MyFaces failures instantly rather than inferring them from user reports of broken forms or ViewExpiredException messages.


Summary

MyFaces failures present as UI rendering errors, not infrastructure failures — users see broken forms or blank fields while the real cause is a session store failure, a phase listener exception returning HTTP 200, or a silently failing EJB timer. External monitoring catches these before they become an incident:

| Monitor Type | What It Covers | |---|---| | HTTP monitor on /api/health | Database, cache, and MyFaces initialization | | HTTP monitor on /actuator/health/myfaces | JSF config state, partial state saving | | Heartbeat monitor (EJB timer) | Scheduled report and data pipeline execution | | Heartbeat monitor (conversation cleanup) | Background CDI conversation management liveness |

Get started free at vigilmon.online — your first MyFaces application monitor is running in under two minutes.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →