For a Technical Architect, installing a monitoring agent and looking at default CPU/Memory dashboards is merely scratching the surface. True observability in an enterprise Adobe Experience Manager (AEM) ecosystem requires deep integration into OSGi, the JCR (Jackrabbit Oak), Apache Sling, and the Dispatcher caching layers.
When a P1 incident occurs—like a Publish tier suffering from thread pool exhaustion or the Dispatcher serving stale content—you need deterministic data, not guesswork.
This guide is an architect-level deep dive into instrumenting, tuning, and utilizing New Relic for AEM (applicable to AEM as a Cloud Service, AMS, and On-Premise).
1. The Architect's Dilemma: Custom Instrumentation vs. AEMaaCS Limitations
By default, the New Relic Java Agent hooks into standard Java EE servlets. Because AEM routes requests through Apache Sling and an OSGi container, the default APM trace will often just show a massive block of time spent in org.apache.sling.engine.impl.SlingMainServlet.
To get true visibility into custom code, you need Custom Instrumentation. However, how you achieve this depends entirely on your hosting model.
The AEM as a Cloud Service (AEMaaCS) Reality
According to official Adobe documentation, AEMaaCS provides a built-in, Adobe-managed New Relic sub-account. It is crucial for architects to understand the limitations of this managed account:
- No Custom Instrumentation: You cannot inject custom XML pointcuts or use
@Traceannotations in the Adobe-managed agent. - No Log Ingestion or Custom Alerts: The built-in account disables log ingestion and custom NRQL alerting.
- The Workaround: To achieve true custom observability on AEMaaCS, architects must provision a Customer-Managed New Relic Account. You then use Cloud Manager to configure Log Forwarding via the New Relic HTTPS Log API. You rely on heavily structured logging in your Java code, which is forwarded to your private New Relic account where you can build custom NRQL alerts and dashboards.
Strategy for AMS & On-Premise: XML & @Trace Annotations
If you are on Adobe Managed Services (AMS) or On-Premise, you control the Java agent and can fully instrument OSGi.
XML-Based Custom Instrumentation:
For third-party bundles, create an extension.xml inside the extensions directory of your New Relic agent folder:
<?xml version="1.0" encoding="UTF-8"?>
<extension xmlns="https://newrelic.com/docs/java/xsd/v1.0" name="AEM_Custom_OSGi_Instrumentation" version="1.0">
<instrumentation>
<!-- Instrument a heavy Sling Model -->
<pointcut transactionStartPoint="false">
<className>com.mycompany.aem.core.models.impl.MegaNavigationModelImpl</className>
<method>
<name>getNavigationItems</name>
</method>
</pointcut>
<!-- Instrument a Sling Job Consumer (Start a new transaction here) -->
<pointcut transactionStartPoint="true">
<className>com.mycompany.aem.core.jobs.AssetSyncJobConsumer</className>
<method>
<name>process</name>
</method>
</pointcut>
</instrumentation>
</extension>Strategy B: The @Trace Annotation
For code you control, add the New Relic API dependency to your core bundle's pom.xml:
<dependency>
<groupId>com.newrelic.agent.java</groupId>
<artifactId>newrelic-api</artifactId>
<version>8.5.0</version>
<scope>provided</scope>
</dependency>Then, annotate complex OSGi service methods. Use dispatcher = true for background tasks (like Sling Jobs or Scheduled Tasks) to ensure they are tracked as standalone transactions rather than lumped into a generic "Background Task" bucket.
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Trace;
import org.osgi.service.component.annotations.Component;
@Component(service = InventorySyncService.class)
public class InventorySyncServiceImpl implements InventorySyncService {
@Trace(dispatcher = true)
public void syncWithERP() {
NewRelic.setTransactionName("Custom", "InventorySyncJob");
// Add custom metadata for NRQL querying
NewRelic.addCustomParameter("syncSource", "SAP_ERP");
try {
executeComplexSync();
} catch (Exception e) {
NewRelic.noticeError(e);
throw e;
}
}
}2. Scraping AEM's Heartbeat: Custom JMX Integrations (AMS & On-Premise)
AEM exposes its most critical health metrics via Java Management Extensions (JMX). The New Relic Java Agent can scrape these MBeans automatically if configured, allowing you to build architect-level dashboards.
Create a jmx.yml file in your newrelic/extensions folder to capture specific AEM MBeans:
name: AEM_Critical_JMX_Metrics
version: 1.0
enabled: true
jmx:
# 1. Track Replication Queue Backlogs
- object_name: "com.adobe.granite.replication:type=agent,id=publish"
metrics:
- attributes: QueueSize
type: simple
- attributes: Status
type: simple
# 2. Track Jackrabbit Oak Repository Sessions (Detect ResourceResolver leaks)
- object_name: "org.apache.jackrabbit.oak:name=Sessions,type=SessionStats"
metrics:
- attributes: SessionCount
type: simple
# 3. Track TarMK Segment Store Size (Monitor for Compaction failures)
- object_name: "org.apache.jackrabbit.oak:name=TarMK disk usage,type=SegmentRevisionGarbageCollection"
metrics:
- attributes: RepositorySize
type: simpleArchitect's NRQL Use Case:
You can now create a New Relic Alert that triggers before a system crash.
Query: SELECT latest(QueueSize) FROM JmxBuiltIn WHERE objectName LIKE '%id=publish%'
Alert Threshold: Trigger P2 if QueueSize > 5000 for more than 15 minutes.
3. Distributed Tracing: The CDN -> Dispatcher -> Publisher Pipeline
Modern AEM architectures utilize CDNs (Fastly/Akamai) in front of Apache Dispatcher, which proxies to AEM Publishers. When a request is slow, where is the latency introduced?
To trace this, you must pass unique trace IDs through the stack and ingest them into New Relic.
Step 1: Dispatcher Configuration (Apache)
Modify your httpd.conf to generate a unique Request ID and inject it into both the request headers (to pass to AEM) and the Apache access log.
# Generate unique ID
RewriteEngine On
RewriteCond %{ENV:UNIQUE_ID} ^$
RewriteRule ^ - [E=UNIQUE_ID:%{UNIQUE_ID}]
# Pass to AEM
RequestHeader set X-Request-Id "%{UNIQUE_ID}e"
# Log it in Dispatcher access.log for New Relic Infra Agent to parse
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %D %{UNIQUE_ID}e %{X-Cache}o" combined_with_trace
CustomLog "logs/access_log" combined_with_traceStep 2: AEM Sling Filter (Java)
Create a global Sling Filter in AEM to read the X-Request-Id and bind it to the New Relic transaction.
import com.newrelic.api.agent.NewRelic;
// ... (OSGi annotations)
public class TraceIdFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest req = (HttpServletRequest) request;
String traceId = req.getHeader("X-Request-Id");
if (traceId != null) {
// Bind the Apache Unique ID to the New Relic Transaction
NewRelic.addCustomParameter("cdnDispatcherTraceId", traceId);
}
chain.doFilter(request, response);
}
}Now, when a user complains about a 10-second page load, you take the Trace ID from the browser's response headers, query it in NRQL (SELECT * FROM Transaction WHERE cdnDispatcherTraceId = 'abc-123'), and immediately see if the time was spent in AEM or if the Dispatcher took 9 seconds waiting for a backend connection timeout.
4. Frontend Observability: SPA Editor & Edge Delivery Services
If your AEM implementation uses the SPA Editor (React/Angular) or Edge Delivery Services (formerly Project Helix), standard page-load metrics are insufficient.
Single Page Application (SPA) Tracking
For AEM SPA Editor, a route change doesn't trigger a full browser reload. You must use the New Relic Browser API to manually record interactions.
In your React App's Router configuration:
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
export function NewRelicRouteTracker() {
const location = useLocation();
useEffect(() => {
if (typeof newrelic !== 'undefined') {
// End previous interaction and start a new one
newrelic.setCurrentRouteName(location.pathname);
newrelic.interaction().setName(`Route: ${location.pathname}`).save();
}
}, [location]);
return null;
}Edge Delivery Services (EDS)
EDS relies heavily on edge caching and Fastly/Cloudflare. Because there is no heavy Java backend rendering the HTML, New Relic RUM (Browser) becomes your primary source of truth. Focus your NRQL dashboards on Core Web Vitals:
-- Architect Dashboard: LCP by Device Type for Edge Delivery Site
SELECT percentile(largestContentfulPaint, 75)
FROM PageViewTiming
WHERE appName = 'AEM_Edge_Production'
FACET deviceType TIMESERIES AUTO5. Post-Mortem Walkthrough: Diagnosing ResourceResolver Leaks
Let's put on our Architect hats and walk through a critical production incident using New Relic.
The Incident: At 2:00 PM on Cyber Monday, the AEM Author instance completely locks up. The JVM process is running, but no users can log in, and the UI spins endlessly.
The Architect's Diagnosis in New Relic:
- APM > JVMs > Memory: You check the Heap graph. The Heap is at 95% and erratic, but it's not a complete OutOfMemory (OOM) flatline.
- APM > JVMs > Threads: You look at the Thread Count. The number of active threads spiked from 200 to 1,400 right at 2:00 PM. We have thread pool exhaustion.
- Thread Profiler: You trigger a 2-minute Thread Profile in New Relic. The resulting tree shows that 1,100 threads are in a
BLOCKEDstate, waiting on a monitor lock insideorg.apache.jackrabbit.oak.plugins.segment.SegmentNodeStore. - JMX Metrics Dashboard: You check the custom JMX dashboard we built earlier. The
SessionCountmetric shows 85,000 open JCR sessions. - Logs in Context: You query the AEM
error.logvia New Relic:SELECT * FROM Log WHERE message LIKE '%Unclosed ResourceResolver%' SINCE 2 hours agoThe logs point directly tocom.mycompany.aem.core.listeners.ProductUpdateListener.
The Root Cause & Fix:
A custom OSGi Event Listener was triggering on every commerce product sync. Inside the listener, resourceResolverFactory.getServiceResourceResolver() was called, but the resolver was not wrapped in a try-with-resources block or explicitly closed in a finally block.
The massive influx of Cyber Monday product updates leaked thousands of sessions, exhausting the Oak repository's connection pool and blocking all subsequent threads.
Fix: Push a hotfix wrapping the resolver in a try-with-resources block, and configure New Relic to alert if JCR Session Count exceeds 5,000.
Conclusion: Shifting from Monitoring to Observability
Monitoring is looking at a dashboard to see if CPU is high. Observability is being able to ask arbitrary, ad-hoc questions about your system and getting immediate answers based on telemetry data.
For an AEM Architect, New Relic is the lens through which you understand your system's behavior under stress. By implementing custom OSGi pointcuts, scraping Jackrabbit JMX metrics, injecting distributed trace IDs through your Dispatcher, and precisely measuring SPA route changes, you transition your team from reactive firefighters to proactive engineers.
Official References & Documentation
To dive deeper into the specific configurations mentioned in this guide, refer to the official documentation from both Adobe and New Relic:
- New Relic One on AEM as a Cloud Service - Official Adobe documentation covering the limitations and features of the built-in Adobe-managed New Relic account.
- AEMaaCS Custom Log Forwarding - How to configure Cloud Manager to forward AEM logs to a customer-managed New Relic instance via the HTTPS Log API.
- New Relic Java Agent: Custom Instrumentation - Official guide on writing XML pointcuts and using the
@Traceannotation for custom Java profiling. - New Relic: Custom JMX Monitoring - Detailed syntax for configuring
jmx.ymlto scrape custom MBeans like Apache Jackrabbit Oak.
Discussion
Loading discussion…
Try a related tool
Subscribe to the Newsletter
Get the latest articles, tutorials, and tech insights delivered straight to your inbox. No spam, unsubscribe anytime.

