for the full
+ * license.
+ */
+package com.vaadin.observability.micrometer;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import com.vaadin.pro.licensechecker.BuildType;
+import com.vaadin.pro.licensechecker.Capabilities;
+import com.vaadin.pro.licensechecker.Capability;
+import com.vaadin.pro.licensechecker.LicenseChecker;
+import com.vaadin.pro.licensechecker.LicenseException;
+import com.vaadin.pro.licensechecker.MissingLicenseKeyException;
+
+/**
+ * Validates the Observability Kit commercial license.
+ *
+ * Unlike a license-checking {@code VaadinServiceInitListener}, this does not
+ * fail application startup. It is consulted by
+ * {@link MetricsServiceInitListener} as a gate: when the kit is not licensed,
+ * the instrumentation (meter and observation registries, binders and trackers)
+ * is simply not registered, and the application keeps running without
+ * telemetry.
+ */
+final class ObservabilityLicense {
+
+ static final String PROPERTIES_RESOURCE = "observability-kit.properties";
+
+ static final String VERSION_PROPERTY = "observability-kit.version";
+
+ static final String PRODUCT_NAME = "vaadin-observability-kit";
+
+ static final String PRODUCT_VERSION;
+
+ static {
+ final var properties = loadAllProperties(PROPERTIES_RESOURCE);
+ PRODUCT_VERSION = properties.getProperty(VERSION_PROPERTY);
+ }
+
+ private ObservabilityLicense() {
+ }
+
+ /**
+ * Checks whether Observability Kit is licensed for use.
+ *
+ * Production builds are validated at build time, so this returns
+ * {@code true} without a runtime check. Development builds are validated
+ * against the local license key; a missing, invalid or outdated key makes
+ * this return {@code false} instead of throwing, so the caller can skip
+ * registering instrumentation rather than failing the whole application.
+ *
+ * @param productionMode
+ * whether the deployment runs in production mode
+ * @return {@code true} if the kit may register its instrumentation
+ */
+ static boolean isLicensed(boolean productionMode) {
+ if (productionMode) {
+ return true;
+ }
+ try {
+ // A null BuildType allows trial licensing builds. The no-key
+ // handler throws instead of opening a browser so the check stays
+ // non-blocking, and the zero timeout avoids waiting for a download.
+ BuildType buildType = null;
+ LicenseChecker.checkLicense(PRODUCT_NAME, PRODUCT_VERSION,
+ buildType, url -> {
+ throw new MissingLicenseKeyException(
+ "No license key present");
+ }, 0, Capabilities.of(Capability.PRE_TRIAL));
+ return true;
+ } catch (LicenseException e) {
+ return false;
+ }
+ }
+
+ static Properties loadAllProperties(String propertiesResource) {
+ final var cl = ObservabilityLicense.class.getClassLoader();
+ try (final var stream = cl.getResourceAsStream(propertiesResource)) {
+ final var properties = new Properties();
+ properties.load(stream);
+ return properties;
+ } catch (NullPointerException | IOException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+}
diff --git a/observability-kit-micrometer/src/main/resources/observability-kit.properties b/observability-kit-micrometer/src/main/resources/observability-kit.properties
new file mode 100644
index 0000000..d2fa87a
--- /dev/null
+++ b/observability-kit-micrometer/src/main/resources/observability-kit.properties
@@ -0,0 +1 @@
+observability-kit.version=${project.version}
diff --git a/observability-kit-micrometer/src/test/java/com/vaadin/observability/micrometer/MetricsServiceInitListenerLicenseTest.java b/observability-kit-micrometer/src/test/java/com/vaadin/observability/micrometer/MetricsServiceInitListenerLicenseTest.java
new file mode 100644
index 0000000..29e4b39
--- /dev/null
+++ b/observability-kit-micrometer/src/test/java/com/vaadin/observability/micrometer/MetricsServiceInitListenerLicenseTest.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright (C) 2000-2026 Vaadin Ltd
+ *
+ * This program is available under Vaadin Commercial License and Service Terms.
+ *
+ * See for the full
+ * license.
+ */
+package com.vaadin.observability.micrometer;
+
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.vaadin.flow.server.ServiceInitEvent;
+import com.vaadin.flow.server.UIInitListener;
+import com.vaadin.flow.server.VaadinRequestInterceptor;
+import com.vaadin.flow.server.VaadinService;
+import com.vaadin.pro.licensechecker.LicenseChecker;
+import com.vaadin.pro.licensechecker.LicenseException;
+
+import static com.vaadin.observability.micrometer.ObservabilityLicense.loadAllProperties;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class MetricsServiceInitListenerLicenseTest {
+
+ private VaadinService service;
+ private ServiceInitEvent event;
+
+ @BeforeEach
+ void setUp() {
+ service = mock(VaadinService.class, RETURNS_DEEP_STUBS);
+ event = mock(ServiceInitEvent.class);
+ when(event.getSource()).thenReturn(service);
+ ObservabilityKit.install(new SimpleMeterRegistry(),
+ ObservabilitySettings.builder().build());
+ }
+
+ @AfterEach
+ void tearDown() {
+ ObservabilityKit.reset();
+ }
+
+ @Test
+ void developmentMode_withoutValidLicense_skipsInstrumentation() {
+ when(service.getDeploymentConfiguration().isProductionMode())
+ .thenReturn(false);
+
+ try (var licenseChecker = mockStatic(LicenseChecker.class)) {
+ licenseChecker
+ .when(() -> LicenseChecker.checkLicense(any(), any(), any(),
+ any(), anyInt(), any()))
+ .thenThrow(new LicenseException("no valid license"));
+
+ new MetricsServiceInitListener().serviceInit(event);
+ }
+
+ verify(service, never()).addSessionInitListener(any());
+ verify(service, never()).addUIInitListener(any());
+ verify(event, never()).addVaadinRequestInterceptor(any());
+ }
+
+ @Test
+ void developmentMode_withValidLicense_registersInstrumentation() {
+ when(service.getDeploymentConfiguration().isProductionMode())
+ .thenReturn(false);
+
+ // An unstubbed static checkLicense is a no-op, i.e. a valid license
+ try (var licenseChecker = mockStatic(LicenseChecker.class)) {
+ new MetricsServiceInitListener().serviceInit(event);
+ }
+
+ verify(service).addUIInitListener(any(UIInitListener.class));
+ verify(event).addVaadinRequestInterceptor(
+ any(VaadinRequestInterceptor.class));
+ }
+
+ @Test
+ void productionMode_registersWithoutCheckingLicense() {
+ when(service.getDeploymentConfiguration().isProductionMode())
+ .thenReturn(true);
+
+ try (var licenseChecker = mockStatic(LicenseChecker.class)) {
+ new MetricsServiceInitListener().serviceInit(event);
+
+ // Production builds are validated at build time, not at runtime
+ licenseChecker.verifyNoInteractions();
+ }
+
+ verify(service).addUIInitListener(any(UIInitListener.class));
+ }
+
+ @Test
+ void loadAllProperties_throwsError_whenResourceMissing() {
+ assertThrows(ExceptionInInitializerError.class,
+ () -> loadAllProperties("non-existent.properties"));
+ }
+}
diff --git a/observability-kit-micrometer/src/test/java/com/vaadin/observability/micrometer/MetricsServiceInitListenerTest.java b/observability-kit-micrometer/src/test/java/com/vaadin/observability/micrometer/MetricsServiceInitListenerTest.java
index 98572f2..076429d 100644
--- a/observability-kit-micrometer/src/test/java/com/vaadin/observability/micrometer/MetricsServiceInitListenerTest.java
+++ b/observability-kit-micrometer/src/test/java/com/vaadin/observability/micrometer/MetricsServiceInitListenerTest.java
@@ -22,6 +22,7 @@
import com.vaadin.flow.server.communication.RpcInvocationListener;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -35,11 +36,22 @@ void tearDown() {
ObservabilityKit.reset();
}
+ /**
+ * A production-mode service, so the license gate passes without a runtime
+ * license check and these tests can focus on binder registration.
+ */
+ private static VaadinService licensedService() {
+ VaadinService service = mock(VaadinService.class, RETURNS_DEEP_STUBS);
+ when(service.getDeploymentConfiguration().isProductionMode())
+ .thenReturn(true);
+ return service;
+ }
+
@Test
void registersSessionBinderWhenSessionsEnabled() {
ObservabilityKit.install(new SimpleMeterRegistry(),
ObservabilitySettings.builder().build());
- VaadinService service = mock(VaadinService.class);
+ VaadinService service = licensedService();
ServiceInitEvent event = mock(ServiceInitEvent.class);
when(event.getSource()).thenReturn(service);
@@ -66,7 +78,7 @@ void doesNothingWhenNotInstalled() {
void skipsSessionBinderWhenSessionsDisabled() {
ObservabilityKit.install(new SimpleMeterRegistry(),
ObservabilitySettings.builder().sessions(false).build());
- VaadinService service = mock(VaadinService.class);
+ VaadinService service = licensedService();
ServiceInitEvent event = mock(ServiceInitEvent.class);
when(event.getSource()).thenReturn(service);
@@ -81,7 +93,7 @@ void skipsSessionBinderWhenSessionsDisabled() {
void registersUiInitListenerWhenUisEnabled() {
ObservabilityKit.install(new SimpleMeterRegistry(),
ObservabilitySettings.builder().build());
- VaadinService service = mock(VaadinService.class);
+ VaadinService service = licensedService();
ServiceInitEvent event = mock(ServiceInitEvent.class);
when(event.getSource()).thenReturn(service);
@@ -95,7 +107,7 @@ void registersUiInitListenerWhenNavigationEnabledAndUisDisabled() {
ObservabilityKit.install(new SimpleMeterRegistry(),
ObservabilitySettings.builder().uis(false).navigation(true)
.build());
- VaadinService service = mock(VaadinService.class);
+ VaadinService service = licensedService();
ServiceInitEvent event = mock(ServiceInitEvent.class);
when(event.getSource()).thenReturn(service);
@@ -109,7 +121,7 @@ void skipsUiInitListenerWhenUisAndNavigationAndClientDisabled() {
ObservabilityKit.install(new SimpleMeterRegistry(),
ObservabilitySettings.builder().uis(false).navigation(false)
.client(false).build());
- VaadinService service = mock(VaadinService.class);
+ VaadinService service = licensedService();
ServiceInitEvent event = mock(ServiceInitEvent.class);
when(event.getSource()).thenReturn(service);
@@ -123,7 +135,7 @@ void registersUiInitListenerWhenOnlyClientEnabled() {
ObservabilityKit.install(new SimpleMeterRegistry(),
ObservabilitySettings.builder().uis(false).navigation(false)
.client(true).build());
- VaadinService service = mock(VaadinService.class);
+ VaadinService service = licensedService();
ServiceInitEvent event = mock(ServiceInitEvent.class);
when(event.getSource()).thenReturn(service);
@@ -136,7 +148,7 @@ void registersUiInitListenerWhenOnlyClientEnabled() {
void registersRequestInterceptorWhenRequestsEnabled() {
ObservabilityKit.install(new SimpleMeterRegistry(),
ObservabilitySettings.builder().build());
- VaadinService service = mock(VaadinService.class);
+ VaadinService service = licensedService();
ServiceInitEvent event = mock(ServiceInitEvent.class);
when(event.getSource()).thenReturn(service);
@@ -151,7 +163,7 @@ void registersRequestInterceptorWhenOnlyErrorsEnabled() {
ObservabilityKit.install(new SimpleMeterRegistry(),
ObservabilitySettings.builder().requests(false).errors(true)
.build());
- VaadinService service = mock(VaadinService.class);
+ VaadinService service = licensedService();
ServiceInitEvent event = mock(ServiceInitEvent.class);
when(event.getSource()).thenReturn(service);
@@ -166,7 +178,7 @@ void skipsRequestInterceptorWhenRequestsAndErrorsDisabled() {
ObservabilityKit.install(new SimpleMeterRegistry(),
ObservabilitySettings.builder().requests(false).errors(false)
.build());
- VaadinService service = mock(VaadinService.class);
+ VaadinService service = licensedService();
ServiceInitEvent event = mock(ServiceInitEvent.class);
when(event.getSource()).thenReturn(service);
@@ -179,7 +191,7 @@ void skipsRequestInterceptorWhenRequestsAndErrorsDisabled() {
void registersRpcInvocationListenerWhenRequestsEnabled() {
ObservabilityKit.install(new SimpleMeterRegistry(),
ObservabilitySettings.builder().build());
- VaadinService service = mock(VaadinService.class);
+ VaadinService service = licensedService();
ServiceInitEvent event = mock(ServiceInitEvent.class);
when(event.getSource()).thenReturn(service);
@@ -193,7 +205,7 @@ void registersRpcInvocationListenerWhenRequestsEnabled() {
void skipsRpcInvocationListenerWhenRequestsDisabled() {
ObservabilityKit.install(new SimpleMeterRegistry(),
ObservabilitySettings.builder().requests(false).build());
- VaadinService service = mock(VaadinService.class);
+ VaadinService service = licensedService();
ServiceInitEvent event = mock(ServiceInitEvent.class);
when(event.getSource()).thenReturn(service);
@@ -207,7 +219,7 @@ void skipsRpcInvocationListenerWhenOnlyErrorsEnabled() {
ObservabilityKit.install(new SimpleMeterRegistry(),
ObservabilitySettings.builder().requests(false).errors(true)
.build());
- VaadinService service = mock(VaadinService.class);
+ VaadinService service = licensedService();
ServiceInitEvent event = mock(ServiceInitEvent.class);
when(event.getSource()).thenReturn(service);
diff --git a/observability-kit-micrometer/src/test/java/com/vaadin/observability/micrometer/MetricsServiceInitListenerTracesTest.java b/observability-kit-micrometer/src/test/java/com/vaadin/observability/micrometer/MetricsServiceInitListenerTracesTest.java
index 6eab547..17e106d 100644
--- a/observability-kit-micrometer/src/test/java/com/vaadin/observability/micrometer/MetricsServiceInitListenerTracesTest.java
+++ b/observability-kit-micrometer/src/test/java/com/vaadin/observability/micrometer/MetricsServiceInitListenerTracesTest.java
@@ -37,7 +37,10 @@ void executorIsWrappedWhenTracesEnabled() {
new SimpleMeterRegistry(), obs,
ObservabilitySettings.builder().build());
- VaadinService service = Mockito.mock(VaadinService.class);
+ VaadinService service = Mockito.mock(VaadinService.class,
+ Mockito.RETURNS_DEEP_STUBS);
+ Mockito.when(service.getDeploymentConfiguration().isProductionMode())
+ .thenReturn(true);
ServiceInitEvent event = new ServiceInitEvent(service);
Executor original = Runnable::run;
event.setExecutor(original);
@@ -57,7 +60,10 @@ void executorIsNotWrappedWhenTracesDisabled() {
new SimpleMeterRegistry(), obs,
ObservabilitySettings.builder().traces(false).build());
- VaadinService service = Mockito.mock(VaadinService.class);
+ VaadinService service = Mockito.mock(VaadinService.class,
+ Mockito.RETURNS_DEEP_STUBS);
+ Mockito.when(service.getDeploymentConfiguration().isProductionMode())
+ .thenReturn(true);
ServiceInitEvent event = new ServiceInitEvent(service);
Executor original = Runnable::run;
event.setExecutor(original);
@@ -74,7 +80,10 @@ void executorIsNotWrappedWhenObservationRegistryAbsent() {
new SimpleMeterRegistry(), null,
ObservabilitySettings.builder().build());
- VaadinService service = Mockito.mock(VaadinService.class);
+ VaadinService service = Mockito.mock(VaadinService.class,
+ Mockito.RETURNS_DEEP_STUBS);
+ Mockito.when(service.getDeploymentConfiguration().isProductionMode())
+ .thenReturn(true);
ServiceInitEvent event = new ServiceInitEvent(service);
Executor original = Runnable::run;
event.setExecutor(original);
diff --git a/pom.xml b/pom.xml
index cb47b85..767975c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -47,6 +47,7 @@
UTF-8
3.13.0
+ 3.4.1
3.2.1
3.4.1
3.2.5
@@ -149,6 +150,11 @@
maven-compiler-plugin
${maven.compiler.version}
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${maven.jar.version}
+
org.apache.maven.plugins
maven-source-plugin