Rollbar is a cloud-based error monitoring platform that helps developers detect, diagnose, and resolve errors in real-time. This guide covers comprehensive integration patterns for Java applications.
Core Concepts
What is Rollbar?
- Real-time error tracking and monitoring platform
- Supports multiple programming languages and frameworks
- Provides error grouping, notifications, and workflow integration
- Offers deployment tracking and source map support
Key Features:
- Real-time error monitoring
- Smart error grouping
- Customizable notifications
- Deployment tracking
- Team collaboration tools
- Integrations with Slack, Jira, etc.
Dependencies and Setup
Maven Dependencies
<properties>
<rollbar.version>1.9.0</rollbar.version>
<spring-boot.version>3.1.0</spring-boot.version>
<logback.version>1.4.11</logback.version>
</properties>
<dependencies>
<!-- Rollbar Java SDK -->
<dependency>
<groupId>com.rollbar</groupId>
<artifactId>rollbar-java</artifactId>
<version>${rollbar.version}</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Logback Appender -->
<dependency>
<groupId>com.rollbar</groupId>
<artifactId>rollbar-appender</artifactId>
<version>${rollbar.version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
</dependencies>
Gradle Dependencies
dependencies {
implementation 'com.rollbar:rollbar-java:1.9.0'
implementation 'com.rollbar:rollbar-appender:1.9.0'
implementation 'org.springframework.boot:spring-boot-starter-web:3.1.0'
implementation 'org.springframework.boot:spring-boot-starter-aop:3.1.0'
implementation 'ch.qos.logback:logback-classic:1.4.11'
}
Core Implementation
1. Rollbar Configuration
@Configuration
@ConfigurationProperties(prefix = "rollbar")
public class RollbarConfig {
private String accessToken;
private String environment = "development";
private String codeVersion;
private String framework = "spring-boot";
private boolean enabled = true;
private boolean handleUncaughtErrors = true;
private int timeout = 3000;
private int retries = 3;
// Getters and setters
public String getAccessToken() { return accessToken; }
public void setAccessToken(String accessToken) { this.accessToken = accessToken; }
public String getEnvironment() { return environment; }
public void setEnvironment(String environment) { this.environment = environment; }
public String getCodeVersion() { return codeVersion; }
public void setCodeVersion(String codeVersion) { this.codeVersion = codeVersion; }
public String getFramework() { return framework; }
public void setFramework(String framework) { this.framework = framework; }
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public boolean isHandleUncaughtErrors() { return handleUncaughtErrors; }
public void setHandleUncaughtErrors(boolean handleUncaughtErrors) { this.handleUncaughtErrors = handleUncaughtErrors; }
public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; }
public int getRetries() { return retries; }
public void setRetries(int retries) { this.retries = retries; }
}
2. Rollbar Provider
@Component
public class RollbarProvider {
private final RollbarConfig config;
private Rollbar rollbar;
private static final Logger logger = LoggerFactory.getLogger(RollbarProvider.class);
public RollbarProvider(RollbarConfig config) {
this.config = config;
initializeRollbar();
}
private void initializeRollbar() {
if (!config.isEnabled()) {
logger.info("Rollbar is disabled");
return;
}
try {
ConfigBuilder configBuilder = new ConfigBuilder(config.getAccessToken(), config.getEnvironment())
.codeVersion(config.getCodeVersion())
.framework(config.getFramework())
.handleUncaughtErrors(config.isHandleUncaughtErrors())
.timeout(config.getTimeout())
.retries(config.getRetries())
.enabled(config.isEnabled());
this.rollbar = new Rollbar(configBuilder.build());
// Add custom data transformer
this.rollbar.addCustomData(new CustomDataTransformer());
logger.info("Rollbar initialized successfully for environment: {}", config.getEnvironment());
} catch (Exception e) {
logger.error("Failed to initialize Rollbar", e);
this.rollbar = null;
}
}
public Rollbar getRollbar() {
return rollbar;
}
public boolean isEnabled() {
return rollbar != null;
}
public void close() {
if (rollbar != null) {
try {
rollbar.close(true); // Wait for pending items
logger.info("Rollbar closed successfully");
} catch (Exception e) {
logger.error("Error closing Rollbar", e);
}
}
}
}
// Custom data transformer for adding additional context
class CustomDataTransformer implements CustomData {
@Override
public Object customData() {
Map<String, Object> customData = new HashMap<>();
customData.put("server_host", getServerHost());
customData.put("jvm_version", System.getProperty("java.version"));
customData.put("available_processors", Runtime.getRuntime().availableProcessors());
customData.put("timestamp", Instant.now().toString());
return customData;
}
private String getServerHost() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (Exception e) {
return "unknown";
}
}
}
3. Error Tracking Service
@Component
public class ErrorTrackingService {
private final RollbarProvider rollbarProvider;
private final ObjectMapper objectMapper;
private static final Logger logger = LoggerFactory.getLogger(ErrorTrackingService.class);
public ErrorTrackingService(RollbarProvider rollbarProvider) {
this.rollbarProvider = rollbarProvider;
this.objectMapper = new ObjectMapper();
}
public void trackError(Throwable throwable) {
trackError(throwable, null, null);
}
public void trackError(Throwable throwable, String message) {
trackError(throwable, message, null);
}
public void trackError(Throwable throwable, String message, Map<String, Object> context) {
if (!rollbarProvider.isEnabled()) {
logger.debug("Rollbar disabled, skipping error tracking: {}",
throwable != null ? throwable.getMessage() : message);
return;
}
try {
Rollbar rollbar = rollbarProvider.getRollbar();
if (message != null && throwable != null) {
rollbar.error(throwable, context, message);
} else if (throwable != null) {
rollbar.error(throwable, context);
} else if (message != null) {
rollbar.error(message, context);
}
logger.debug("Error tracked in Rollbar: {}",
throwable != null ? throwable.getMessage() : message);
} catch (Exception e) {
logger.error("Failed to track error in Rollbar", e);
}
}
public void trackWarning(String message) {
trackWarning(message, null);
}
public void trackWarning(String message, Map<String, Object> context) {
if (!rollbarProvider.isEnabled()) {
return;
}
try {
rollbarProvider.getRollbar().warning(message, context);
logger.debug("Warning tracked in Rollbar: {}", message);
} catch (Exception e) {
logger.error("Failed to track warning in Rollbar", e);
}
}
public void trackInfo(String message) {
trackInfo(message, null);
}
public void trackInfo(String message, Map<String, Object> context) {
if (!rollbarProvider.isEnabled()) {
return;
}
try {
rollbarProvider.getRollbar().info(message, context);
logger.debug("Info tracked in Rollbar: {}", message);
} catch (Exception e) {
logger.error("Failed to track info in Rollbar", e);
}
}
public void trackDebug(String message) {
trackDebug(message, null);
}
public void trackDebug(String message, Map<String, Object> context) {
if (!rollbarProvider.isEnabled()) {
return;
}
try {
rollbarProvider.getRollbar().debug(message, context);
logger.debug("Debug tracked in Rollbar: {}", message);
} catch (Exception e) {
logger.error("Failed to track debug in Rollbar", e);
}
}
public void trackMessage(Level level, String message, Map<String, Object> context) {
if (!rollbarProvider.isEnabled()) {
return;
}
try {
rollbarProvider.getRollbar().log(level, message, context);
} catch (Exception e) {
logger.error("Failed to track message in Rollbar", e);
}
}
public void trackWithPersonContext(String userId, String email, String username,
Throwable throwable, String message) {
Map<String, Object> personContext = new HashMap<>();
personContext.put("id", userId);
personContext.put("email", email);
personContext.put("username", username);
Person person = new Person.Builder()
.id(userId)
.email(email)
.username(username)
.build();
if (!rollbarProvider.isEnabled()) {
return;
}
try {
Rollbar rollbar = rollbarProvider.getRollbar();
if (throwable != null) {
rollbar.error(throwable, personContext, message);
} else {
rollbar.error(message, personContext);
}
logger.debug("Error tracked with person context: {}", userId);
} catch (Exception e) {
logger.error("Failed to track error with person context", e);
}
}
}
4. Context Builder
@Component
public class RollbarContextBuilder {
public Map<String, Object> buildHttpRequestContext(HttpServletRequest request) {
Map<String, Object> context = new HashMap<>();
context.put("request_method", request.getMethod());
context.put("request_url", request.getRequestURL().toString());
context.put("query_string", request.getQueryString());
context.put("user_agent", request.getHeader("User-Agent"));
context.put("client_ip", getClientIp(request));
context.put("session_id", request.getSession(false) != null ?
request.getSession().getId() : null);
// Add headers (excluding sensitive ones)
Map<String, String> headers = new HashMap<>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
if (!isSensitiveHeader(headerName)) {
headers.put(headerName, request.getHeader(headerName));
}
}
context.put("headers", headers);
return context;
}
public Map<String, Object> buildBusinessContext(String operation, String entityType,
String entityId, String userId) {
Map<String, Object> context = new HashMap<>();
context.put("operation", operation);
context.put("entity_type", entityType);
context.put("entity_id", entityId);
context.put("user_id", userId);
context.put("timestamp", Instant.now().toString());
return context;
}
public Map<String, Object> buildDatabaseContext(String operation, String table,
String query, long duration) {
Map<String, Object> context = new HashMap<>();
context.put("db_operation", operation);
context.put("db_table", table);
context.put("db_query", sanitizeQuery(query));
context.put("db_duration_ms", duration);
context.put("db_timestamp", Instant.now().toString());
return context;
}
public Map<String, Object> buildIntegrationContext(String serviceName, String operation,
String endpoint, int statusCode) {
Map<String, Object> context = new HashMap<>();
context.put("integration_service", serviceName);
context.put("integration_operation", operation);
context.put("integration_endpoint", endpoint);
context.put("integration_status", statusCode);
context.put("integration_timestamp", Instant.now().toString());
return context;
}
private String getClientIp(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0];
}
return request.getRemoteAddr();
}
private boolean isSensitiveHeader(String headerName) {
String lowerHeader = headerName.toLowerCase();
return lowerHeader.contains("auth") ||
lowerHeader.contains("token") ||
lowerHeader.contains("password") ||
lowerHeader.contains("cookie");
}
private String sanitizeQuery(String query) {
if (query == null) return null;
// Remove sensitive data from queries
return query.replaceAll("(?i)password\\s*=\\s*'[^']*'", "password='***'")
.replaceAll("(?i)token\\s*=\\s*'[^']*'", "token='***'");
}
}
Spring Boot Integration
1. Application Properties
# application.yml
rollbar:
access-token: ${ROLLBAR_ACCESS_TOKEN:test-token}
environment: ${ROLLBAR_ENVIRONMENT:development}
code-version: ${APP_VERSION:1.0.0}
framework: spring-boot
enabled: ${ROLLBAR_ENABLED:true}
handle-uncaught-errors: true
timeout: 3000
retries: 3
# Logback appender configuration
appender:
enabled: true
threshold: ERROR
include-formatted-message: true
2. Auto-Configuration
@Configuration
@EnableConfigurationProperties(RollbarProperties.class)
@ConditionalOnProperty(name = "rollbar.enabled", havingValue = "true")
public class RollbarAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RollbarConfig rollbarConfig(RollbarProperties properties) {
RollbarConfig config = new RollbarConfig();
config.setAccessToken(properties.getAccessToken());
config.setEnvironment(properties.getEnvironment());
config.setCodeVersion(properties.getCodeVersion());
config.setFramework(properties.getFramework());
config.setEnabled(properties.isEnabled());
config.setHandleUncaughtErrors(properties.isHandleUncaughtErrors());
config.setTimeout(properties.getTimeout());
config.setRetries(properties.getRetries());
return config;
}
@Bean
@ConditionalOnMissingBean
public RollbarProvider rollbarProvider(RollbarConfig config) {
return new RollbarProvider(config);
}
@Bean
@ConditionalOnMissingBean
public ErrorTrackingService errorTrackingService(RollbarProvider rollbarProvider) {
return new ErrorTrackingService(rollbarProvider);
}
@Bean
@ConditionalOnMissingBean
public RollbarContextBuilder rollbarContextBuilder() {
return new RollbarContextBuilder();
}
@Bean
public GlobalExceptionHandler globalExceptionHandler(ErrorTrackingService errorTrackingService,
RollbarContextBuilder contextBuilder) {
return new GlobalExceptionHandler(errorTrackingService, contextBuilder);
}
}
@ConfigurationProperties(prefix = "rollbar")
public class RollbarProperties {
private String accessToken;
private String environment = "development";
private String codeVersion;
private String framework = "spring-boot";
private boolean enabled = true;
private boolean handleUncaughtErrors = true;
private int timeout = 3000;
private int retries = 3;
private Appender appender = new Appender();
// Getters and setters
public static class Appender {
private boolean enabled = true;
private String threshold = "ERROR";
private boolean includeFormattedMessage = true;
// getters and setters
}
}
3. Global Exception Handler
@ControllerAdvice
public class GlobalExceptionHandler {
private final ErrorTrackingService errorTrackingService;
private final RollbarContextBuilder contextBuilder;
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
public GlobalExceptionHandler(ErrorTrackingService errorTrackingService,
RollbarContextBuilder contextBuilder) {
this.errorTrackingService = errorTrackingService;
this.contextBuilder = contextBuilder;
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception e,
HttpServletRequest request) {
// Build context
Map<String, Object> context = contextBuilder.buildHttpRequestContext(request);
context.put("handler", "GlobalExceptionHandler");
context.put("exception_type", e.getClass().getSimpleName());
// Track error
errorTrackingService.trackError(e, "Unhandled exception in API", context);
logger.error("Unhandled exception in API: {}", request.getRequestURI(), e);
ErrorResponse error = ErrorResponse.builder()
.errorCode("INTERNAL_ERROR")
.message("An unexpected error occurred")
.timestamp(Instant.now())
.path(request.getRequestURI())
.build();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e,
HttpServletRequest request) {
Map<String, Object> context = contextBuilder.buildHttpRequestContext(request);
context.put("business_error_code", e.getErrorCode());
context.put("operation", e.getOperation());
errorTrackingService.trackError(e, "Business exception occurred", context);
ErrorResponse error = ErrorResponse.builder()
.errorCode(e.getErrorCode())
.message(e.getMessage())
.timestamp(Instant.now())
.path(request.getRequestURI())
.build();
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException e,
HttpServletRequest request) {
Map<String, Object> context = contextBuilder.buildHttpRequestContext(request);
context.put("resource_type", e.getResourceType());
context.put("resource_id", e.getResourceId());
errorTrackingService.trackError(e, "Resource not found", context);
ErrorResponse error = ErrorResponse.builder()
.errorCode("NOT_FOUND")
.message(e.getMessage())
.timestamp(Instant.now())
.path(request.getRequestURI())
.build();
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(ValidationException.class)
public ResponseEntity<ErrorResponse> handleValidationException(ValidationException e,
HttpServletRequest request) {
Map<String, Object> context = contextBuilder.buildHttpRequestContext(request);
context.put("validation_errors", e.getValidationErrors());
errorTrackingService.trackError(e, "Validation failed", context);
ErrorResponse error = ErrorResponse.builder()
.errorCode("VALIDATION_ERROR")
.message("Request validation failed")
.details(e.getValidationErrors())
.timestamp(Instant.now())
.path(request.getRequestURI())
.build();
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
}
// Error Response DTO
public class ErrorResponse {
private final String errorCode;
private final String message;
private final Instant timestamp;
private final String path;
private final Map<String, Object> details;
private ErrorResponse(Builder builder) {
this.errorCode = builder.errorCode;
this.message = builder.message;
this.timestamp = builder.timestamp;
this.path = builder.path;
this.details = builder.details;
}
// Getters
public String getErrorCode() { return errorCode; }
public String getMessage() { return message; }
public Instant getTimestamp() { return timestamp; }
public String getPath() { return path; }
public Map<String, Object> getDetails() { return details; }
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String errorCode;
private String message;
private Instant timestamp;
private String path;
private Map<String, Object> details = new HashMap<>();
public Builder errorCode(String errorCode) {
this.errorCode = errorCode;
return this;
}
public Builder message(String message) {
this.message = message;
return this;
}
public Builder timestamp(Instant timestamp) {
this.timestamp = timestamp;
return this;
}
public Builder path(String path) {
this.path = path;
return this;
}
public Builder details(Map<String, Object> details) {
this.details = details;
return this;
}
public Builder detail(String key, Object value) {
this.details.put(key, value);
return this;
}
public ErrorResponse build() {
return new ErrorResponse(this);
}
}
}
4. Spring AOP for Method-Level Error Tracking
@Aspect
@Component
public class ErrorTrackingAspect {
private final ErrorTrackingService errorTrackingService;
private final RollbarContextBuilder contextBuilder;
private static final Logger logger = LoggerFactory.getLogger(ErrorTrackingAspect.class);
public ErrorTrackingAspect(ErrorTrackingService errorTrackingService,
RollbarContextBuilder contextBuilder) {
this.errorTrackingService = errorTrackingService;
this.contextBuilder = contextBuilder;
}
@Around("@annotation(TrackExecution)")
public Object trackMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().toShortString();
Object[] args = joinPoint.getArgs();
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - startTime;
// Track successful execution if needed
if (shouldTrackSuccess(methodName)) {
Map<String, Object> context = Map.of(
"method", methodName,
"duration_ms", duration,
"status", "success"
);
errorTrackingService.trackInfo("Method executed successfully", context);
}
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
// Build context for error tracking
Map<String, Object> context = buildMethodContext(joinPoint, duration, args);
// Track the error
errorTrackingService.trackError(e,
String.format("Method execution failed: %s", methodName), context);
logger.error("Method execution failed: {}", methodName, e);
throw e;
}
}
@AfterThrowing(pointcut = "@annotation(TrackError)", throwing = "ex")
public void trackCustomError(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().toShortString();
Map<String, Object> context = buildMethodContext(joinPoint, 0, joinPoint.getArgs());
errorTrackingService.trackError(ex,
String.format("Custom error in method: %s", methodName), context);
}
private Map<String, Object> buildMethodContext(JoinPoint joinPoint, long duration, Object[] args) {
Map<String, Object> context = new HashMap<>();
context.put("method", joinPoint.getSignature().toShortString());
context.put("class", joinPoint.getTarget().getClass().getSimpleName());
context.put("duration_ms", duration);
context.put("timestamp", Instant.now().toString());
// Add method arguments (sanitized)
if (args != null && args.length > 0) {
Map<String, Object> sanitizedArgs = sanitizeArguments(args);
context.put("method_arguments", sanitizedArgs);
}
return context;
}
private Map<String, Object> sanitizeArguments(Object[] args) {
Map<String, Object> sanitized = new HashMap<>();
for (int i = 0; i < args.length; i++) {
String key = "arg_" + i;
Object value = args[i];
if (value instanceof String) {
sanitized.put(key, sanitizeString((String) value));
} else if (value != null) {
sanitized.put(key, value.getClass().getSimpleName());
} else {
sanitized.put(key, "null");
}
}
return sanitized;
}
private String sanitizeString(String value) {
if (value == null) return null;
if (value.length() > 100) {
return value.substring(0, 100) + "...";
}
return value;
}
private boolean shouldTrackSuccess(String methodName) {
// Only track success for important methods to avoid noise
return methodName.contains("process") ||
methodName.contains("create") ||
methodName.contains("update");
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TrackExecution {
boolean trackSuccess() default false;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TrackError {
String level() default "error";
}
5. Service Layer Integration
@Service
@Transactional
public class UserService {
private final ErrorTrackingService errorTrackingService;
private final RollbarContextBuilder contextBuilder;
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
public UserService(ErrorTrackingService errorTrackingService,
RollbarContextBuilder contextBuilder) {
this.errorTrackingService = errorTrackingService;
this.contextBuilder = contextBuilder;
}
@TrackExecution
public User createUser(CreateUserRequest request) {
try {
logger.info("Creating user: {}", request.getEmail());
// Validate user
validateUser(request);
// Create user
User user = userRepository.save(request.toUser());
// Track successful creation
Map<String, Object> context = contextBuilder.buildBusinessContext(
"create", "user", user.getId(), user.getId());
errorTrackingService.trackInfo("User created successfully", context);
return user;
} catch (ValidationException e) {
// Track validation error with context
Map<String, Object> context = contextBuilder.buildBusinessContext(
"create", "user", null, null);
context.put("validation_errors", e.getErrors());
errorTrackingService.trackError(e, "User validation failed", context);
throw e;
} catch (Exception e) {
// Track unexpected error
Map<String, Object> context = contextBuilder.buildBusinessContext(
"create", "user", null, null);
errorTrackingService.trackError(e, "Unexpected error creating user", context);
throw new UserCreationException("Failed to create user", e);
}
}
@TrackExecution(trackSuccess = true)
public User updateUser(String userId, UpdateUserRequest request) {
User user = userRepository.findById(userId)
.orElseThrow(() -> {
ResourceNotFoundException ex = new ResourceNotFoundException("user", userId);
// Track not found error
Map<String, Object> context = contextBuilder.buildBusinessContext(
"update", "user", userId, null);
errorTrackingService.trackError(ex, "User not found for update", context);
return ex;
});
user.update(request);
return userRepository.save(user);
}
@TrackError(level = "warning")
public void processUserBatch(List<String> userIds) {
int successCount = 0;
int failureCount = 0;
for (String userId : userIds) {
try {
processSingleUser(userId);
successCount++;
} catch (Exception e) {
failureCount++;
// Track individual failure but continue processing
Map<String, Object> context = contextBuilder.buildBusinessContext(
"batch_process", "user", userId, userId);
context.put("batch_position", userIds.indexOf(userId));
errorTrackingService.trackError(e,
"Failed to process user in batch", context);
}
}
// Track batch completion summary
if (failureCount > 0) {
Map<String, Object> context = Map.of(
"total_users", userIds.size(),
"success_count", successCount,
"failure_count", failureCount,
"success_rate", (double) successCount / userIds.size() * 100
);
errorTrackingService.trackWarning(
String.format("Batch processing completed with %d failures", failureCount),
context);
}
}
private void validateUser(CreateUserRequest request) {
// Validation logic
if (request.getEmail() == null || !request.getEmail().contains("@")) {
throw new ValidationException("Invalid email address");
}
}
}
6. REST Controller Integration
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
private final ErrorTrackingService errorTrackingService;
private final RollbarContextBuilder contextBuilder;
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
public UserController(UserService userService, ErrorTrackingService errorTrackingService,
RollbarContextBuilder contextBuilder) {
this.userService = userService;
this.errorTrackingService = errorTrackingService;
this.contextBuilder = contextBuilder;
}
@PostMapping
public ResponseEntity<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request,
HttpServletRequest httpRequest) {
try {
User user = userService.createUser(request);
// Track successful API call
Map<String, Object> context = contextBuilder.buildHttpRequestContext(httpRequest);
context.put("user_id", user.getId());
errorTrackingService.trackInfo("User creation API completed successfully", context);
return ResponseEntity.status(HttpStatus.CREATED).body(UserResponse.from(user));
} catch (ValidationException e) {
Map<String, Object> context = contextBuilder.buildHttpRequestContext(httpRequest);
context.put("validation_errors", e.getErrors());
errorTrackingService.trackError(e, "User creation validation failed", context);
return ResponseEntity.badRequest().build();
}
}
@GetMapping("/{userId}")
public ResponseEntity<UserResponse> getUser(@PathVariable String userId,
HttpServletRequest httpRequest) {
try {
User user = userService.findById(userId);
if (user == null) {
ResourceNotFoundException ex = new ResourceNotFoundException("user", userId);
Map<String, Object> context = contextBuilder.buildHttpRequestContext(httpRequest);
context.put("user_id", userId);
errorTrackingService.trackError(ex, "User not found in API", context);
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(UserResponse.from(user));
} catch (Exception e) {
Map<String, Object> context = contextBuilder.buildHttpRequestContext(httpRequest);
context.put("user_id", userId);
errorTrackingService.trackError(e, "Unexpected error fetching user", context);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/{userId}/orders")
public ResponseEntity<List<OrderResponse>> getUserOrders(@PathVariable String userId,
@RequestParam(defaultValue = "0") int page,
HttpServletRequest httpRequest) {
try {
List<Order> orders = userService.getUserOrders(userId, page);
// Track API usage
Map<String, Object> context = contextBuilder.buildHttpRequestContext(httpRequest);
context.put("user_id", userId);
context.put("page", page);
context.put("order_count", orders.size());
errorTrackingService.trackInfo("User orders fetched successfully", context);
List<OrderResponse> responses = orders.stream()
.map(OrderResponse::from)
.collect(Collectors.toList());
return ResponseEntity.ok(responses);
} catch (Exception e) {
Map<String, Object> context = contextBuilder.buildHttpRequestContext(httpRequest);
context.put("user_id", userId);
context.put("page", page);
errorTrackingService.trackError(e, "Failed to fetch user orders", context);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
Logback Appender Configuration
1. Logback Configuration
<!-- src/main/resources/logback-spring.xml -->
<configuration>
<!-- Rollbar Appender -->
<appender name="ROLLBAR" class="com.rollbar.logback.RollbarAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<accessToken>${ROLLBAR_ACCESS_TOKEN:-test-token}</accessToken>
<environment>${ROLLBAR_ENVIRONMENT:-development}</environment>
<codeVersion>${APP_VERSION:-1.0.0}</codeVersion>
<includeFormattedMessage>true</includeFormattedMessage>
</appender>
<!-- Console Appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- Async Rollbar Appender for Better Performance -->
<appender name="ASYNC_ROLLBAR" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="ROLLBAR" />
<queueSize>10000</queueSize>
<discardingThreshold>0</discardingThreshold>
<includeCallerData>true</includeCallerData>
</appender>
<!-- Root Logger -->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="ASYNC_ROLLBAR" />
</root>
<!-- Specific loggers -->
<logger name="com.example.service" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE" />
<appender-ref ref="ASYNC_ROLLBAR" />
</logger>
</configuration>
2. Custom Logback Appender
public class CustomRollbarAppender extends RollingFileAppender<ILoggingEvent> {
private final ErrorTrackingService errorTrackingService;
public CustomRollbarAppender() {
// This would be injected via Spring in a real scenario
this.errorTrackingService = ApplicationContextHolder.getBean(ErrorTrackingService.class);
}
@Override
protected void append(ILoggingEvent event) {
super.append(event);
// Send ERROR and WARN levels to Rollbar
if (event.getLevel().isGreaterOrEqual(Level.WARN)) {
trackLogEvent(event);
}
}
private void trackLogEvent(ILoggingEvent event) {
try {
Map<String, Object> context = new HashMap<>();
context.put("logger", event.getLoggerName());
context.put("thread", event.getThreadName());
context.put("level", event.getLevel().toString());
if (event.getThrowableProxy() != null) {
Throwable throwable = extractThrowable(event);
errorTrackingService.trackError(throwable, event.getFormattedMessage(), context);
} else {
errorTrackingService.trackMessage(
convertLevel(event.getLevel()),
event.getFormattedMessage(),
context
);
}
} catch (Exception e) {
// Don't let Rollbar tracking break logging
System.err.println("Failed to track log event in Rollbar: " + e.getMessage());
}
}
private Throwable extractThrowable(ILoggingEvent event) {
if (event.getThrowableProxy() instanceof ThrowableProxy) {
return ((ThrowableProxy) event.getThrowableProxy()).getThrowable();
}
return new Exception(event.getFormattedMessage());
}
private Level convertLevel(ch.qos.logback.classic.Level logbackLevel) {
switch (logbackLevel.levelInt) {
case ch.qos.logback.classic.Level.ERROR_INT:
return Level.ERROR;
case ch.qos.logback.classic.Level.WARN_INT:
return Level.WARNING;
case ch.qos.logback.classic.Level.INFO_INT:
return Level.INFO;
case ch.qos.logback.classic.Level.DEBUG_INT:
return Level.DEBUG;
default:
return Level.INFO;
}
}
}
Deployment Tracking
1. Deployment Tracker
@Component
public class DeploymentTracker {
private final RollbarProvider rollbarProvider;
private final RollbarConfig config;
private static final Logger logger = LoggerFactory.getLogger(DeploymentTracker.class);
public DeploymentTracker(RollbarProvider rollbarProvider, RollbarConfig config) {
this.rollbarProvider = rollbarProvider;
this.config = config;
}
public void trackDeployment(String revision, String comment, String user) {
if (!rollbarProvider.isEnabled()) {
logger.debug("Rollbar disabled, skipping deployment tracking");
return;
}
try {
Rollbar rollbar = rollbarProvider.getRollbar();
// Create deployment
Deployment deployment = new Deployment.Builder()
.environment(config.getEnvironment())
.revision(revision)
.comment(comment)
.localUsername(user)
.build();
// Track deployment
rollbar.track(deployment);
logger.info("Deployment tracked in Rollbar: {} - {}", revision, comment);
} catch (Exception e) {
logger.error("Failed to track deployment in Rollbar", e);
}
}
@EventListener
public void onApplicationReady(ApplicationReadyEvent event) {
// Automatically track deployment/startup
String revision = getGitRevision();
trackDeployment(
revision,
"Application started successfully",
"system"
);
}
private String getGitRevision() {
try {
// Read from git properties file
Properties gitProps = new Properties();
try (InputStream input = getClass().getResourceAsStream("/git.properties")) {
if (input != null) {
gitProps.load(input);
return gitProps.getProperty("git.commit.id.abbrev", "unknown");
}
}
} catch (Exception e) {
logger.debug("Failed to read git revision", e);
}
return "unknown";
}
}
Testing
1. Unit Tests
@ExtendWith(MockitoExtension.class)
class ErrorTrackingServiceTest {
@Mock
private RollbarProvider rollbarProvider;
@Mock
private Rollbar rollbar;
private ErrorTrackingService errorTrackingService;
@BeforeEach
void setUp() {
when(rollbarProvider.isEnabled()).thenReturn(true);
when(rollbarProvider.getRollbar()).thenReturn(rollbar);
errorTrackingService = new ErrorTrackingService(rollbarProvider);
}
@Test
void shouldTrackErrorWhenEnabled() {
// Given
Exception testException = new RuntimeException("Test error");
// When
errorTrackingService.trackError(testException, "Test message");
// Then
verify(rollbar).error(eq(testException), any(), eq("Test message"));
}
@Test
void shouldNotTrackErrorWhenDisabled() {
// Given
when(rollbarProvider.isEnabled()).thenReturn(false);
Exception testException = new RuntimeException("Test error");
// When
errorTrackingService.trackError(testException, "Test message");
// Then
verify(rollbar, never()).error(any(), any(), any());
}
}
@SpringBootTest
@TestPropertySource(properties = {
"rollbar.access-token=test-token",
"rollbar.environment=test",
"rollbar.enabled=true"
})
class RollbarIntegrationTest {
@Autowired
private ErrorTrackingService errorTrackingService;
@Test
void shouldTrackErrorInRealEnvironment() {
// Given
Exception testException = new RuntimeException("Integration test error");
// When
errorTrackingService.trackError(testException, "Integration test");
// Then - no exception should be thrown
assertThatNoException().isThrownBy(() ->
errorTrackingService.trackError(testException, "Integration test"));
}
}
Best Practices
- Error Filtering: Don't track expected business exceptions as errors
- Context Enrichment: Always provide meaningful context
- Performance: Use async logging appenders
- Security: Sanitize sensitive data from context
- Monitoring: Track error rates and patterns
- Alerting: Set up proper notification rules in Rollbar
// Example of error filtering
@Component
public class ErrorFilter {
private final Set<Class<?>> ignoredExceptions = Set.of(
ValidationException.class,
ResourceNotFoundException.class
);
public boolean shouldTrack(Throwable throwable) {
return !ignoredExceptions.contains(throwable.getClass());
}
}
// Enhanced error tracking service with filtering
@Component
public class FilteredErrorTrackingService {
private final ErrorTrackingService errorTrackingService;
private final ErrorFilter errorFilter;
public void trackErrorFiltered(Throwable throwable, String message, Map<String, Object> context) {
if (errorFilter.shouldTrack(throwable)) {
errorTrackingService.trackError(throwable, message, context);
} else {
// Log locally but don't send to Rollbar
LoggerFactory.getLogger(throwable.getClass())
.warn("Filtered error (not sent to Rollbar): {}", message, throwable);
}
}
}
Conclusion
Rollbar integration in Java provides:
- Real-time error monitoring and alerting
- Rich context for faster debugging
- Smart error grouping to reduce noise
- Deployment tracking for correlation
- Comprehensive integrations with existing logging
By implementing the patterns shown above, you can build robust error tracking that helps you identify and resolve issues faster while maintaining application performance and developer productivity.