New Relic Java Agent: Complete Implementation Guide

New Relic provides comprehensive application performance monitoring (APM) for Java applications. This guide demonstrates how to integrate and leverage the New Relic Java Agent for monitoring, tracing, and performance analysis.

Why Use New Relic Java Agent?

  • Application Performance Monitoring: Real-time performance insights
  • Distributed Tracing: End-to-end transaction tracing
  • Error Analytics: Detailed error tracking and analysis
  • Custom Metrics: Business-specific performance metrics
  • Infrastructure Monitoring: Server and container metrics
  • Alerting: Proactive performance alerts

Prerequisites

  • New Relic Account with license key
  • Java 8+ application
  • Maven/Gradle for dependency management

Step 1: Installation and Dependencies

Maven (pom.xml):

<dependencies>
<!-- New Relic Agent (provided scope for agent jar) -->
<dependency>
<groupId>com.newrelic.agent.java</groupId>
<artifactId>newrelic-java</artifactId>
<version>8.0.0</version>
<scope>provided</scope>
</dependency>
<!-- New Relic API for custom instrumentation -->
<dependency>
<groupId>com.newrelic.agent.java</groupId>
<artifactId>newrelic-api</artifactId>
<version>8.0.0</version>
</dependency>
<!-- Spring Boot Starter (Optional) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.0</version>
</dependency>
<!-- Micrometer for custom metrics -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>1.10.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Agent setup plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.4.0</version>
<executions>
<execution>
<id>copy-newrelic</id>
<phase>package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>com.newrelic.agent.java</groupId>
<artifactId>newrelic-java</artifactId>
<version>8.0.0</version>
<type>jar</type>
<overWrite>true</overWrite>
<outputDirectory>${project.build.directory}</outputDirectory>
<destFileName>newrelic.jar</destFileName>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

Step 2: New Relic Configuration

newrelic.yml:

# New Relic Configuration File
common: &default_settings
# License key
license_key: ${NEW_RELIC_LICENSE_KEY}
# Application name
app_name: ${NEW_RELIC_APP_NAME:My Java Application}
# Logging configuration
log_level: info
log_file_path: logs
log_file_name: newrelic.log
# Security settings
security:
enabled: false
agent_token: 
record_sql: obfuscated
attributes:
include_enabled: true
exclude: 
- request.headers.cookie
- request.headers.authorization
- response.headers.set-cookie
# Transaction tracing
transaction_tracer:
enabled: true
transaction_threshold: apdex_f
record_sql: obfuscated
stack_trace_threshold: 0.5
explain_enabled: true
explain_threshold: 0.5
# Error collection
error_collector:
enabled: true
ignore_errors: 
# Browser monitoring
browser_monitoring:
enabled: true
auto_instrument: true
# Thread profiling
thread_profiler:
enabled: true
# Slow queries
slow_sql:
enabled: true
max_samples: 10
# Development environment
development:
<<: *default_settings
app_name: My App (Development)
transaction_tracer:
enabled: true
record_sql: raw
# Production environment  
production:
<<: *default_settings
app_name: My App (Production)
log_level: info
security:
enabled: true

newrelic.env:

# Environment variables for New Relic
NEW_RELIC_APP_NAME=My Java Application
NEW_RELIC_LICENSE_KEY=your_license_key_here
NEW_RELIC_LOG_LEVEL=info
NEW_RELIC_DISTRIBUTED_TRACING_ENABLED=true
NEW_RELIC_SPAN_EVENTS_ENABLED=true
NEW_RELIC_LOG_FORWARDING_ENABLED=true

Step 3: Application Configuration

application.yml:

# Spring Boot Application Configuration
spring:
application:
name: order-service
# New Relic specific properties
newrelic:
enabled: true
config:
file: classpath:newrelic.yml
environment: ${NEW_RELIC_ENVIRONMENT:development}
# Custom New Relic properties
newrelic:
app-name: ${spring.application.name}
license-key: ${NEW_RELIC_LICENSE_KEY:}
log-level: info
distributed-tracing:
enabled: true
custom-insights-events:
enabled: true
labels:
environment: ${spring.profiles.active:local}
version: 1.0.0
team: backend-services
# Logging configuration for New Relic
logging:
level:
com.newrelic: INFO
NewRelic: INFO
file:
path: ./logs

Step 4: New Relic Service Classes

@Service
@Slf4j
public class NewRelicMonitoringService {
private final NewRelicApiClient newRelicApiClient;
public NewRelicMonitoringService(NewRelicApiClient newRelicApiClient) {
this.newRelicApiClient = newRelicApiClient;
}
// Custom metric recording
public void recordCustomMetric(String metricName, double value) {
NewRelic.recordMetric(metricName, value);
log.debug("Recorded custom metric: {} = {}", metricName, value);
}
public void recordResponseTime(String operation, long durationMs) {
String metricName = "Custom/ResponseTime/" + operation;
NewRelic.recordMetric(metricName, durationMs);
// Also record as response time metric
NewRelic.recordResponseTimeMetric(operation, durationMs);
}
// Business transaction monitoring
public void recordBusinessTransaction(String transactionName, long duration, boolean success) {
String metricName = "BusinessTransaction/" + transactionName;
NewRelic.recordMetric(metricName, duration);
if (!success) {
NewRelic.recordMetric(metricName + "/Errors", 1);
}
// Add custom attributes
NewRelic.addCustomParameter("business_transaction", transactionName);
NewRelic.addCustomParameter("transaction_duration", duration);
NewRelic.addCustomParameter("transaction_success", success);
}
// Error tracking
public void recordError(Throwable error, Map<String, String> context) {
NewRelic.noticeError(error);
// Add context as custom parameters
if (context != null) {
context.forEach(NewRelic::addCustomParameter);
}
log.error("Error recorded in New Relic: {}", error.getMessage(), error);
}
public void recordError(String errorMessage, Map<String, String> context) {
NewRelic.noticeError(errorMessage);
if (context != null) {
context.forEach(NewRelic::addCustomParameter);
}
log.error("Error message recorded in New Relic: {}", errorMessage);
}
// Custom event tracking
public void recordCustomEvent(String eventType, Map<String, Object> attributes) {
NewRelic.recordCustomEvent(eventType, attributes);
log.debug("Recorded custom event: {} with {} attributes", eventType, attributes.size());
}
// Distributed tracing
public void addTracingAttributes(Map<String, String> attributes) {
if (attributes != null) {
attributes.forEach(NewRelic::addCustomParameter);
}
}
public String getTraceId() {
try {
return NewRelic.getAgent().getTransaction().getTraceId();
} catch (Exception e) {
log.warn("Failed to get trace ID from New Relic", e);
return "unknown";
}
}
public boolean isAgentConnected() {
try {
return NewRelic.getAgent().getConnected();
} catch (Exception e) {
return false;
}
}
// Performance monitoring
public <T> T monitorPerformance(String operationName, Supplier<T> operation) {
long startTime = System.currentTimeMillis();
boolean success = false;
try {
T result = operation.get();
success = true;
return result;
} catch (Exception e) {
recordError(e, Map.of("operation", operationName));
throw e;
} finally {
long duration = System.currentTimeMillis() - startTime;
recordResponseTime(operationName, duration);
recordBusinessTransaction(operationName, duration, success);
}
}
public void monitorPerformance(String operationName, Runnable operation) {
long startTime = System.currentTimeMillis();
boolean success = false;
try {
operation.run();
success = true;
} catch (Exception e) {
recordError(e, Map.of("operation", operationName));
throw e;
} finally {
long duration = System.currentTimeMillis() - startTime;
recordResponseTime(operationName, duration);
recordBusinessTransaction(operationName, duration, success);
}
}
}
@Component
@Slf4j
public class NewRelicApiClient {
private final RestTemplate restTemplate;
private final String apiKey;
private final String accountId;
@Value("${newrelic.api.base-url:https://api.newrelic.com}")
private String baseUrl;
public NewRelicApiClient(
@Value("${newrelic.api.key}") String apiKey,
@Value("${newrelic.account.id}") String accountId,
RestTemplateBuilder restTemplateBuilder) {
this.apiKey = apiKey;
this.accountId = accountId;
this.restTemplate = restTemplateBuilder.build();
}
// Query New Relic Insights
public Map<String, Object> queryInsights(String nrql) {
try {
String url = baseUrl + "/v2/query?nrql=" + URLEncoder.encode(nrql, StandardCharsets.UTF_8);
HttpHeaders headers = new HttpHeaders();
headers.set("X-Query-Key", apiKey);
headers.set("Content-Type", "application/json");
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, entity, Map.class);
return response.getBody();
} catch (Exception e) {
log.error("Failed to query New Relic Insights", e);
throw new RuntimeException("New Relic API call failed", e);
}
}
// Get application summary
public Map<String, Object> getApplicationSummary(String applicationId) {
String nrql = String.format(
"SELECT average(duration), percentage(count(*), WHERE error IS true) as errorRate " +
"FROM Transaction WHERE appId = '%s' SINCE 1 hour ago", applicationId);
return queryInsights(nrql);
}
// Get error rates
public double getErrorRate(String applicationId, Duration since) {
String nrql = String.format(
"SELECT percentage(count(*), WHERE error IS true) as errorRate " +
"FROM Transaction WHERE appId = '%s' SINCE %d minutes ago", 
applicationId, since.toMinutes());
Map<String, Object> result = queryInsights(nrql);
// Parse result and return error rate
return parseErrorRate(result);
}
// Get slow transactions
public List<Map<String, Object>> getSlowTransactions(String applicationId, int limit) {
String nrql = String.format(
"SELECT * FROM Transaction WHERE appId = '%s' " +
"WHERE duration > 1.0 LIMIT %d SINCE 1 hour ago", 
applicationId, limit);
Map<String, Object> result = queryInsights(nrql);
return parseTransactions(result);
}
private double parseErrorRate(Map<String, Object> result) {
// Implementation to parse error rate from NRQL result
return 0.0;
}
private List<Map<String, Object>> parseTransactions(Map<String, Object> result) {
// Implementation to parse transactions from NRQL result
return Collections.emptyList();
}
}

Step 5: Custom Instrumentation

// Custom method annotation for New Relic tracing
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Trace {
String metricName() default "";
String category() default "Custom";
boolean dispatcher() default false;
}
// Aspect for @Trace annotation
@Aspect
@Component
@Slf4j
public class TraceAspect {
private final NewRelicMonitoringService monitoringService;
public TraceAspect(NewRelicMonitoringService monitoringService) {
this.monitoringService = monitoringService;
}
@Around("@annotation(trace)")
public Object traceMethod(ProceedingJoinPoint joinPoint, Trace trace) throws Throwable {
String metricName = getMetricName(joinPoint, trace);
return monitoringService.monitorPerformance(metricName, () -> {
try {
// Add method parameters as custom attributes
addMethodParametersToNewRelic(joinPoint);
return joinPoint.proceed();
} catch (Throwable throwable) {
if (throwable instanceof RuntimeException) {
throw (RuntimeException) throwable;
} else {
throw new RuntimeException(throwable);
}
}
});
}
private String getMetricName(ProceedingJoinPoint joinPoint, Trace trace) {
if (!trace.metricName().isEmpty()) {
return trace.metricName();
}
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
return trace.category() + "/" + className + "/" + methodName;
}
private void addMethodParametersToNewRelic(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String[] parameterNames = getParameterNames(joinPoint);
for (int i = 0; i < args.length; i++) {
String paramName = parameterNames != null && i < parameterNames.length ? 
parameterNames[i] : "param" + i;
Object paramValue = args[i];
if (paramValue != null) {
NewRelic.addCustomParameter("method_param_" + paramName, paramValue.toString());
}
}
}
private String[] getParameterNames(ProceedingJoinPoint joinPoint) {
// Implementation to get parameter names (using reflection or Spring's ParameterNameDiscoverer)
return null;
}
}
// Custom WebClient instrumentation
@Component
@Slf4j
public class NewRelicWebClientFilter {
public ExchangeFilterFunction newRelicTracingFilter() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
// Add distributed tracing headers
ClientRequest tracedRequest = ClientRequest.from(clientRequest)
.headers(headers -> addDistributedTracingHeaders(headers))
.build();
return Mono.just(tracedRequest);
});
}
private void addDistributedTracingHeaders(HttpHeaders headers) {
try {
// Add New Relic distributed tracing headers
headers.add("newrelic", NewRelic.getAgent().getTransaction().getRequestMetadata());
} catch (Exception e) {
log.warn("Failed to add New Relic tracing headers", e);
}
}
}
// Custom database instrumentation
@Component
@Slf4j
public class NewRelicJdbcInterceptor {
@Autowired
private NewRelicMonitoringService monitoringService;
public void interceptQuery(String sql, long duration, boolean success) {
String metricName = "Database/Query";
monitoringService.recordResponseTime(metricName, duration);
if (!success) {
monitoringService.recordError("Database query failed", 
Map.of("sql", sql, "duration", String.valueOf(duration)));
}
// Add SQL as custom parameter (obfuscated for security)
NewRelic.addCustomParameter("sql_query", obfuscateSql(sql));
NewRelic.addCustomParameter("query_duration_ms", duration);
}
private String obfuscateSql(String sql) {
// Basic SQL obfuscation for security
return sql.replaceAll("\\b\\d+\\b", "?")
.replaceAll("'.*?'", "?");
}
}

Step 6: Spring Boot Integration

@Configuration
@Slf4j
public class NewRelicAutoConfiguration {
@Bean
@ConditionalOnProperty(name = "newrelic.enabled", havingValue = "true")
public NewRelicMonitoringService newRelicMonitoringService(NewRelicApiClient apiClient) {
log.info("Initializing New Relic Monitoring Service");
return new NewRelicMonitoringService(apiClient);
}
@Bean
@ConditionalOnProperty(name = "newrelic.api.key")
public NewRelicApiClient newRelicApiClient(
@Value("${newrelic.api.key}") String apiKey,
@Value("${newrelic.account.id}") String accountId,
RestTemplateBuilder restTemplateBuilder) {
return new NewRelicApiClient(apiKey, accountId, restTemplateBuilder);
}
@Bean
public TraceAspect traceAspect(NewRelicMonitoringService monitoringService) {
return new TraceAspect(monitoringService);
}
@Bean
public NewRelicWebClientFilter newRelicWebClientFilter() {
return new NewRelicWebClientFilter();
}
@Bean
public FilterRegistrationBean<NewRelicFilter> newRelicFilter() {
FilterRegistrationBean<NewRelicFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new NewRelicFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(1);
return registrationBean;
}
}
// Custom servlet filter for New Relic
@Slf4j
public class NewRelicFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
long startTime = System.currentTimeMillis();
try {
// Set transaction name based on request
String transactionName = getTransactionName(httpRequest);
NewRelic.setTransactionName("Custom", transactionName);
// Add request attributes to New Relic
addRequestAttributes(httpRequest);
chain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - startTime;
// Record response metrics
NewRelic.recordMetric("Http/ResponseTime", duration);
NewRelic.addCustomParameter("http_response_code", httpResponse.getStatus());
NewRelic.addCustomParameter("http_duration_ms", duration);
}
}
private String getTransactionName(HttpServletRequest request) {
String method = request.getMethod();
String path = request.getRequestURI();
return method + " " + path;
}
private void addRequestAttributes(HttpServletRequest request) {
NewRelic.addCustomParameter("http_method", request.getMethod());
NewRelic.addCustomParameter("http_path", request.getRequestURI());
NewRelic.addCustomParameter("http_query_string", request.getQueryString());
NewRelic.addCustomParameter("http_user_agent", request.getHeader("User-Agent"));
NewRelic.addCustomParameter("http_referer", request.getHeader("Referer"));
// Client IP
String clientIp = getClientIp(request);
NewRelic.addCustomParameter("client_ip", clientIp);
}
private String getClientIp(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
return request.getRemoteAddr();
}
}

Step 7: Usage Examples

@Service
@Slf4j
public class OrderService {
private final NewRelicMonitoringService monitoringService;
private final OrderRepository orderRepository;
public OrderService(NewRelicMonitoringService monitoringService, 
OrderRepository orderRepository) {
this.monitoringService = monitoringService;
this.orderRepository = orderRepository;
}
@Trace(metricName = "OrderService/processOrder", category = "Business")
public Order processOrder(Order order) {
return monitoringService.monitorPerformance("processOrder", () -> {
// Add business context
NewRelic.addCustomParameter("order_id", order.getId());
NewRelic.addCustomParameter("customer_id", order.getCustomerId());
NewRelic.addCustomParameter("order_amount", order.getAmount());
try {
// Validate order
validateOrder(order);
// Process payment
processPayment(order);
// Update inventory
updateInventory(order);
// Record success event
Map<String, Object> eventAttributes = new HashMap<>();
eventAttributes.put("order_id", order.getId());
eventAttributes.put("amount", order.getAmount());
eventAttributes.put("currency", order.getCurrency());
monitoringService.recordCustomEvent("OrderProcessed", eventAttributes);
return order;
} catch (Exception e) {
// Record error with context
Map<String, String> errorContext = Map.of(
"order_id", order.getId(),
"customer_id", order.getCustomerId(),
"operation", "processOrder"
);
monitoringService.recordError(e, errorContext);
throw e;
}
});
}
@Trace
public List<Order> getOrdersByCustomer(String customerId, int page, int size) {
return monitoringService.monitorPerformance("getOrdersByCustomer", () -> {
NewRelic.addCustomParameter("customer_id", customerId);
NewRelic.addCustomParameter("page", page);
NewRelic.addCustomParameter("size", size);
try {
List<Order> orders = orderRepository.findByCustomerId(customerId, page, size);
// Record custom metric
monitoringService.recordCustomMetric("Orders/Count", orders.size());
return orders;
} catch (Exception e) {
monitoringService.recordError(e, Map.of("customer_id", customerId));
throw e;
}
});
}
public void batchProcessOrders(List<Order> orders) {
monitoringService.monitorPerformance("batchProcessOrders", () -> {
NewRelic.addCustomParameter("batch_size", orders.size());
int successCount = 0;
int errorCount = 0;
for (Order order : orders) {
try {
processOrder(order);
successCount++;
} catch (Exception e) {
errorCount++;
log.error("Failed to process order: {}", order.getId(), e);
}
}
// Record batch metrics
monitoringService.recordCustomMetric("BatchProcessing/SuccessCount", successCount);
monitoringService.recordCustomMetric("BatchProcessing/ErrorCount", errorCount);
monitoringService.recordCustomMetric("BatchProcessing/TotalCount", orders.size());
// Record custom event for batch completion
Map<String, Object> batchEvent = new HashMap<>();
batchEvent.put("total_orders", orders.size());
batchEvent.put("successful_orders", successCount);
batchEvent.put("failed_orders", errorCount);
batchEvent.put("success_rate", (double) successCount / orders.size());
monitoringService.recordCustomEvent("BatchOrderProcessing", batchEvent);
});
}
private void validateOrder(Order order) {
// Validation logic
if (order.getAmount() <= 0) {
throw new IllegalArgumentException("Invalid order amount");
}
}
private void processPayment(Order order) {
// Payment processing logic
monitoringService.monitorPerformance("processPayment", () -> {
// Simulate payment processing
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Payment processing interrupted", e);
}
});
}
private void updateInventory(Order order) {
// Inventory update logic
monitoringService.monitorPerformance("updateInventory", () -> {
// Simulate inventory update
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Inventory update interrupted", e);
}
});
}
}
@RestController
@Slf4j
public class OrderController {
private final OrderService orderService;
private final NewRelicMonitoringService monitoringService;
public OrderController(OrderService orderService, 
NewRelicMonitoringService monitoringService) {
this.orderService = orderService;
this.monitoringService = monitoringService;
}
@PostMapping("/orders")
public ResponseEntity<Order> createOrder(@RequestBody Order order) {
// Add request context to New Relic
monitoringService.addTracingAttributes(Map.of(
"http_method", "POST",
"http_path", "/orders",
"customer_id", order.getCustomerId()
));
try {
Order processedOrder = orderService.processOrder(order);
return ResponseEntity.ok(processedOrder);
} catch (Exception e) {
monitoringService.recordError(e, Map.of(
"endpoint", "/orders",
"http_method", "POST"
));
return ResponseEntity.status(500).build();
}
}
@GetMapping("/orders/customer/{customerId}")
public ResponseEntity<List<Order>> getCustomerOrders(
@PathVariable String customerId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
monitoringService.addTracingAttributes(Map.of(
"http_method", "GET",
"http_path", "/orders/customer/{customerId}",
"customer_id", customerId,
"page", String.valueOf(page),
"size", String.valueOf(size)
));
try {
List<Order> orders = orderService.getOrdersByCustomer(customerId, page, size);
return ResponseEntity.ok(orders);
} catch (Exception e) {
monitoringService.recordError(e, Map.of(
"customer_id", customerId,
"endpoint", "/orders/customer/{customerId}"
));
return ResponseEntity.status(500).build();
}
}
}

Step 8: Custom Metrics and Dashboards

@Component
@Slf4j
public class BusinessMetricsService {
private final NewRelicMonitoringService monitoringService;
private final MeterRegistry meterRegistry;
public BusinessMetricsService(NewRelicMonitoringService monitoringService,
MeterRegistry meterRegistry) {
this.monitoringService = monitoringService;
this.meterRegistry = meterRegistry;
}
// Business KPIs
public void recordOrderKpis(Order order, boolean success, long processingTime) {
// Custom metrics for New Relic
monitoringService.recordCustomMetric("Business/Orders/Total", 1);
monitoringService.recordCustomMetric("Business/Orders/ProcessingTime", processingTime);
if (success) {
monitoringService.recordCustomMetric("Business/Orders/Successful", 1);
monitoringService.recordCustomMetric("Business/Revenue", order.getAmount());
} else {
monitoringService.recordCustomMetric("Business/Orders/Failed", 1);
}
// Micrometer metrics (will be picked up by New Relic agent)
Counter.builder("business.orders.total")
.tag("status", success ? "success" : "failed")
.register(meterRegistry)
.increment();
Timer.builder("business.orders.processing.time")
.register(meterRegistry)
.record(processingTime, TimeUnit.MILLISECONDS);
}
// User behavior metrics
public void recordUserActivity(String userId, String activity, Map<String, String> context) {
Map<String, Object> eventAttributes = new HashMap<>();
eventAttributes.put("user_id", userId);
eventAttributes.put("activity_type", activity);
eventAttributes.putAll(context);
monitoringService.recordCustomEvent("UserActivity", eventAttributes);
monitoringService.recordCustomMetric("Business/UserActivities/" + activity, 1);
}
// System health metrics
public void recordSystemHealth(Map<String, Object> healthMetrics) {
healthMetrics.forEach((key, value) -> {
if (value instanceof Number) {
monitoringService.recordCustomMetric("System/Health/" + key, ((Number) value).doubleValue());
}
});
monitoringService.recordCustomEvent("SystemHealthCheck", healthMetrics);
}
// Performance metrics by tier
public void recordTierPerformance(String tier, String operation, long duration, boolean success) {
String metricName = String.format("Performance/%s/%s", tier, operation);
monitoringService.recordCustomMetric(metricName, duration);
if (!success) {
monitoringService.recordCustomMetric(metricName + "/Errors", 1);
}
}
}
@Component
@Slf4j
public class NewRelicDashboardService {
private final NewRelicApiClient apiClient;
public NewRelicDashboardService(NewRelicApiClient apiClient) {
this.apiClient = apiClient;
}
// Monitor application health
public void checkApplicationHealth(String applicationId) {
Map<String, Object> summary = apiClient.getApplicationSummary(applicationId);
double errorRate = apiClient.getErrorRate(applicationId, Duration.ofMinutes(30));
if (errorRate > 0.05) { // 5% error rate threshold
log.warn("High error rate detected: {}% for application {}", errorRate * 100, applicationId);
// Trigger alert or notification
}
log.info("Application {} health - Error rate: {}%", applicationId, errorRate * 100);
}
// Get performance insights
public void analyzePerformance(String applicationId) {
List<Map<String, Object>> slowTransactions = apiClient.getSlowTransactions(applicationId, 10);
if (!slowTransactions.isEmpty()) {
log.info("Found {} slow transactions for application {}", slowTransactions.size(), applicationId);
slowTransactions.forEach(transaction -> 
log.info("Slow transaction: {}", transaction)
);
}
}
}

Step 9: Testing and Verification

@SpringBootTest
@Slf4j
class NewRelicIntegrationTest {
@Autowired
private NewRelicMonitoringService monitoringService;
@Autowired
private OrderService orderService;
@Test
void testCustomMetrics() {
monitoringService.recordCustomMetric("Test/Metric", 42.0);
monitoringService.recordResponseTime("TestOperation", 150L);
// Verify no exceptions are thrown
assertTrue(monitoringService.isAgentConnected());
}
@Test
void testErrorTracking() {
Exception testException = new RuntimeException("Test error");
Map<String, String> context = Map.of("test_id", "123", "component", "test");
monitoringService.recordError(testException, context);
// Error should be recorded without throwing exception
assertTrue(true);
}
@Test
void testPerformanceMonitoring() {
String result = monitoringService.monitorPerformance("testOperation", () -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "success";
});
assertEquals("success", result);
}
@Test
void testTraceAnnotation() {
Order testOrder = new Order("test-order", "test-customer", 100.0);
// This should be traced by New Relic
Order result = orderService.processOrder(testOrder);
assertNotNull(result);
}
}
// Test configuration
@TestConfiguration
class NewRelicTestConfig {
@Bean
@Primary
public NewRelicMonitoringService testNewRelicMonitoringService() {
return new NewRelicMonitoringService(mock(NewRelicApiClient.class));
}
}

Step 10: Deployment and Runtime Configuration

Dockerfile:

FROM openjdk:11-jre-slim
# Copy New Relic agent
COPY target/newrelic.jar /app/newrelic.jar
COPY newrelic.yml /app/newrelic.yml
# Copy application
COPY target/application.jar /app/application.jar
# Set New Relic environment variables
ENV NEW_RELIC_APP_NAME=my-java-app
ENV NEW_RELIC_LICENSE_KEY=your_license_key
ENV NEW_RELIC_LOG_LEVEL=info
# Java agent configuration
ENV JAVA_OPTS="-javaagent:/app/newrelic.jar"
WORKDIR /app
CMD java $JAVA_OPTS -jar application.jar

Kubernetes Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
name: java-app
spec:
replicas: 3
selector:
matchLabels:
app: java-app
template:
metadata:
labels:
app: java-app
annotations:
newrelic.io/enabled: "true"
newrelic.io/app-name: "my-java-app"
spec:
containers:
- name: java-app
image: my-java-app:latest
env:
- name: NEW_RELIC_APP_NAME
value: "my-java-app"
- name: NEW_RELIC_LICENSE_KEY
valueFrom:
secretKeyRef:
name: newrelic-secret
key: license-key
- name: NEW_RELIC_DISTRIBUTED_TRACING_ENABLED
value: "true"
- name: JAVA_OPTS
value: "-javaagent:/newrelic/newrelic.jar"
volumeMounts:
- name: newrelic-agent
mountPath: /newrelic
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
volumes:
- name: newrelic-agent
configMap:
name: newrelic-agent

Key Features Implemented

  1. APM Integration: Full application performance monitoring
  2. Custom Metrics: Business-specific performance indicators
  3. Distributed Tracing: End-to-end transaction tracking
  4. Error Analytics: Comprehensive error tracking and analysis
  5. Custom Instrumentation: Method-level tracing and monitoring
  6. Business Metrics: Revenue, order processing, user activity tracking
  7. Dashboard Integration: Custom New Relic dashboards and alerts
  8. Microservices Support: Distributed tracing across services

Best Practices

  1. Meaningful Metric Names: Use consistent, descriptive metric names
  2. Context Enrichment: Add relevant business context to transactions
  3. Security: Obfuscate sensitive data in traces and logs
  4. Performance: Use async operations for metric recording where possible
  5. Alerting: Set up meaningful alerts based on business KPIs
  6. Monitoring: Regularly review performance metrics and traces
  7. Documentation: Document custom metrics and their business significance

This comprehensive New Relic Java Agent implementation provides robust application monitoring, performance insights, and business metrics tracking for Java applications in production environments.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper