JustAnalytics for Spring Boot

Step-by-step guide to add analytics, error tracking, and APM to your Spring Boot app

JustAnalytics for Spring Boot

Add distributed tracing, error tracking, structured logging, and infrastructure metrics to your Spring Boot application.

Time to data: 5 minutes

Prerequisites#

  • Spring Boot 3.0+
  • Java 17+ (Kotlin 1.9+ also supported)
  • Gradle or Maven
  • A JustAnalytics account with a Site ID and API key

Add the dependency

Add the JitPack repository, then the dependency:

Gradle (Kotlin DSL):

// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        mavenCentral()
        maven("https://jitpack.io")
    }
}

// build.gradle.kts
dependencies {
    implementation("com.github.specifiedcodes.justanalytics-java:justanalytics-spring-boot-starter:0.1.0")
}

Gradle (Groovy):

// settings.gradle
repositories {
    mavenCentral()
    maven { url 'https://jitpack.io' }
}

// build.gradle
dependencies {
    implementation 'com.github.specifiedcodes.justanalytics-java:justanalytics-spring-boot-starter:0.1.0'
}

Maven:

<!-- pom.xml -->
<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

<dependency>
    <groupId>com.github.specifiedcodes.justanalytics-java</groupId>
    <artifactId>justanalytics-spring-boot-starter</artifactId>
    <version>0.1.0</version>
</dependency>

Configure application properties

Add your credentials to application.yml:

# src/main/resources/application.yml
justanalytics:
  site-id: ${JA_SITE_ID}
  api-key: ${JA_API_KEY}
  service-name: spring-api
  environment: ${SPRING_PROFILES_ACTIVE:development}
  release: ${APP_VERSION:0.0.0}

Or application.properties:

justanalytics.site-id=${JA_SITE_ID}
justanalytics.api-key=${JA_API_KEY}
justanalytics.service-name=spring-api
justanalytics.environment=${SPRING_PROFILES_ACTIVE:development}

The Spring Boot starter auto-configures:

  • SDK initialization from properties
  • Servlet filter for HTTP request tracing
  • RestTemplate interceptor for outgoing HTTP spans
  • WebClient filter for reactive HTTP spans

Automatic request tracing

All incoming HTTP requests are automatically traced via the servlet filter. No annotations needed:

Java:

@RestController
@RequestMapping("/api/users")
public class UserController {

    // Automatically traced as "GET /api/users/{id}"
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
    }

    // Automatically traced as "POST /api/users"
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public User createUser(@RequestBody @Valid CreateUserRequest request) {
        return userService.create(request);
    }
}

Kotlin:

@RestController
@RequestMapping("/api/users")
class UserController(private val userService: UserService) {

    @GetMapping("/{id}")
    fun getUser(@PathVariable id: Long): User =
        userService.findById(id) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    fun createUser(@RequestBody @Valid request: CreateUserRequest): User =
        userService.create(request)
}

Add error tracking

Add a global exception handler:

Java:

import com.justanalytics.JustAnalytics;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Map<String, String> handleException(Exception ex) {
        JustAnalytics.captureException(ex,
            Map.of("source", "global_handler"));
        return Map.of("error", "Internal server error");
    }
}

Kotlin:

import com.justanalytics.JustAnalytics

@RestControllerAdvice
class GlobalExceptionHandler {

    @ExceptionHandler(Exception::class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    fun handleException(ex: Exception): Map<String, String> {
        JustAnalytics.captureException(ex, tags = mapOf("source" to "global_handler"))
        return mapOf("error" to "Internal server error")
    }
}

Verify data is flowing

Start your application and send a test request:

./gradlew bootRun
curl http://localhost:8080/api/users/1

Verify Your Setup

After starting your app and sending a request, check the Traces tab. You should see spans for your Spring Boot endpoints.

Open Dashboard

Advanced Features#

Custom spans (Java)#

import com.justanalytics.JustAnalytics;

@Service
public class OrderService {

    public Order processOrder(CreateOrderRequest request) {
        return JustAnalytics.startSpan("process-order", span -> {
            span.setAttribute("order.items", request.getItems().size());

            Order order = JustAnalytics.startSpan("db.create-order", innerSpan -> {
                return orderRepository.save(new Order(request));
            });

            JustAnalytics.startSpan("send-confirmation", innerSpan -> {
                emailService.sendConfirmation(order);
                return null;
            });

            return order;
        });
    }
}

Custom spans (Kotlin)#

import com.justanalytics.JustAnalytics

@Service
class OrderService(private val orderRepo: OrderRepository) {

    fun processOrder(request: CreateOrderRequest): Order {
        return JustAnalytics.startSpan("process-order") { span ->
            span.setAttribute("order.items", request.items.size.toString())

            val order = JustAnalytics.startSpan("db.create-order") {
                orderRepo.save(Order(request))
            }

            JustAnalytics.startSpan("send-confirmation") {
                emailService.sendConfirmation(order)
            }

            order
        }
    }
}

User identification#

import com.justanalytics.JustAnalytics;
import com.justanalytics.UserInfo;

@Component
public class AuthFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain) throws Exception {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.isAuthenticated()) {
            UserDetails user = (UserDetails) auth.getPrincipal();
            JustAnalytics.setUser(new UserInfo(user.getUsername(), user.getUsername()));
        }
        chain.doFilter(request, response);
    }
}

Logback integration#

Ship logs to JustAnalytics via the Logback appender:

<!-- src/main/resources/logback-spring.xml -->
<configuration>
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <appender name="JUSTANALYTICS" class="com.justanalytics.logback.JustAnalyticsAppender">
    <minimumLevel>INFO</minimumLevel>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="CONSOLE" />
    <appender-ref ref="JUSTANALYTICS" />
  </root>
</configuration>

Custom metrics#

JustAnalytics.recordMetric("custom.queue_size", queueSize,
    Map.of("queue", "orders"));
JustAnalytics.recordMetric("custom.cache_hit_rate", hitRate);

Environment variables#

| Variable | Description | Required | |----------|-------------|----------| | JA_SITE_ID | Your site ID from the dashboard | Yes | | JA_API_KEY | API key (starts with ja_sk_) | Yes | | SPRING_PROFILES_ACTIVE | Used as the environment tag | No | | APP_VERSION | Release version for tracking | No |