diff --git a/src/main/java/org/fireflyframework/observability/FireflyObservabilityEnvironmentPostProcessor.java b/src/main/java/org/fireflyframework/observability/FireflyObservabilityEnvironmentPostProcessor.java
index 99948c6..2fa436d 100644
--- a/src/main/java/org/fireflyframework/observability/FireflyObservabilityEnvironmentPostProcessor.java
+++ b/src/main/java/org/fireflyframework/observability/FireflyObservabilityEnvironmentPostProcessor.java
@@ -37,6 +37,13 @@
*
{@code OTLP} — disables Prometheus scrape, enables OTLP push
* {@code BOTH} — enables both simultaneously
*
+ *
+ * OTLP Transport Translation
+ * Translates the OpenTelemetry {@code OTEL_EXPORTER_OTLP_PROTOCOL} env var
+ * ({@code grpc | http/protobuf | http/json}) into Spring's
+ * {@code management.otlp.tracing.transport} enum ({@code GRPC | HTTP}), moving the
+ * default endpoint to the HTTP port when HTTP is selected. See
+ * {@link #configureOtlpTracingTransport}.
*
* Runs at {@link Ordered#LOWEST_PRECEDENCE} to ensure {@code application.yml}
* properties are already resolved. Existing user-defined
@@ -59,6 +66,15 @@ public class FireflyObservabilityEnvironmentPostProcessor implements Environment
private static final String EXCLUDE_PROPERTY = "spring.autoconfigure.exclude";
private static final String PROMETHEUS_ENABLED = "management.prometheus.metrics.export.enabled";
private static final String OTLP_METRICS_ENABLED = "management.otlp.metrics.export.enabled";
+ private static final String OTLP_TRACING_TRANSPORT = "management.otlp.tracing.transport";
+ private static final String OTLP_TRACING_ENDPOINT = "management.otlp.tracing.endpoint";
+
+ // --- OpenTelemetry SDK environment variables ---
+ private static final String OTEL_PROTOCOL_ENV = "OTEL_EXPORTER_OTLP_PROTOCOL";
+ private static final String OTEL_TRACES_ENDPOINT_ENV = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT";
+ private static final String OTEL_ENDPOINT_ENV = "OTEL_EXPORTER_OTLP_ENDPOINT";
+ // Default OTLP/HTTP traces endpoint (gRPC lives on :4317, HTTP on :4318 with the /v1/traces path).
+ private static final String DEFAULT_HTTP_TRACES_ENDPOINT = "http://localhost:4318/v1/traces";
private static final String PROPERTY_SOURCE_NAME = "fireflyObservabilityPostProcessor";
private static final String DEFAULTS_SOURCE_NAME = "fireflyObservabilityDefaults";
@@ -90,6 +106,7 @@ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringAp
Map props = new LinkedHashMap<>();
configureTracingBridge(environment, props);
configureMetricsExporter(environment, props);
+ configureOtlpTracingTransport(environment, props);
if (!props.isEmpty()) {
environment.getPropertySources().addFirst(
new MapPropertySource(PROPERTY_SOURCE_NAME, props));
@@ -175,6 +192,45 @@ private void configureMetricsExporter(ConfigurableEnvironment environment, Map
+ * The OTel spec values are {@code grpc | http/protobuf | http/json}, but Spring's
+ * {@code Transport} enum only accepts {@code GRPC | HTTP}. Binding the raw spec value
+ * straight to the enum (as the bundled YAML previously did) fails to bind
+ * {@code http/protobuf} and crashes context initialization. We normalize any
+ * {@code http/*} protocol to {@code http} here.
+ *
+ * gRPC and HTTP also listen on different ports/paths ({@code :4317} vs
+ * {@code :4318/v1/traces}). When HTTP is selected without an explicit endpoint, we move
+ * the default off the gRPC port so the exporter does not POST to a gRPC listener at
+ * runtime. An explicit {@code OTEL_EXPORTER_OTLP_(TRACES_)ENDPOINT} always wins.
+ *
+ * No-ops when the protocol variable is unset, leaving the YAML {@code grpc} default in place.
+ */
+ private void configureOtlpTracingTransport(ConfigurableEnvironment environment, Map props) {
+ String protocol = environment.getProperty(OTEL_PROTOCOL_ENV);
+ if (protocol == null || protocol.isBlank()) {
+ return;
+ }
+ boolean http = protocol.trim().toLowerCase().startsWith("http");
+ props.put(OTLP_TRACING_TRANSPORT, http ? "http" : "grpc");
+
+ if (http && !hasExplicitOtlpEndpoint(environment)) {
+ props.put(OTLP_TRACING_ENDPOINT, DEFAULT_HTTP_TRACES_ENDPOINT);
+ }
+ }
+
+ private boolean hasExplicitOtlpEndpoint(ConfigurableEnvironment environment) {
+ return isSet(environment.getProperty(OTEL_TRACES_ENDPOINT_ENV))
+ || isSet(environment.getProperty(OTEL_ENDPOINT_ENV));
+ }
+
+ private static boolean isSet(String value) {
+ return value != null && !value.isBlank();
+ }
+
/**
* Runs after {@code ConfigDataEnvironmentPostProcessor} so that
* {@code application.yml} properties are available when we read the settings.
diff --git a/src/main/resources/application-firefly-observability.yml b/src/main/resources/application-firefly-observability.yml
index c7f3032..2a712bf 100644
--- a/src/main/resources/application-firefly-observability.yml
+++ b/src/main/resources/application-firefly-observability.yml
@@ -87,7 +87,10 @@ management:
otlp:
tracing:
endpoint: ${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT:${OTEL_EXPORTER_OTLP_ENDPOINT:http://localhost:4317}}
- transport: ${OTEL_EXPORTER_OTLP_PROTOCOL:grpc}
+ # grpc default. OTEL_EXPORTER_OTLP_PROTOCOL (grpc|http/protobuf|http/json) is translated to
+ # this enum (GRPC|HTTP) by FireflyObservabilityEnvironmentPostProcessor — binding the raw
+ # spec value here would fail to bind http/protobuf and crash context init.
+ transport: grpc
metrics:
export:
enabled: false # Overridden by EnvironmentPostProcessor based on exporter property
diff --git a/src/test/java/org/fireflyframework/observability/autoconfigure/FireflyObservabilityEnvironmentPostProcessorTest.java b/src/test/java/org/fireflyframework/observability/autoconfigure/FireflyObservabilityEnvironmentPostProcessorTest.java
index 276102d..45fb847 100644
--- a/src/test/java/org/fireflyframework/observability/autoconfigure/FireflyObservabilityEnvironmentPostProcessorTest.java
+++ b/src/test/java/org/fireflyframework/observability/autoconfigure/FireflyObservabilityEnvironmentPostProcessorTest.java
@@ -110,6 +110,70 @@ void metricsBothFlipsAllOn() {
assertThat(environment.getProperty("management.otlp.metrics.export.enabled")).isEqualTo("true");
}
+ @Test
+ void otlpProtocolHttpProtobufTranslatesToHttpTransport() {
+ MockEnvironment environment = new MockEnvironment();
+ // The OTel-spec value that previously crashed context init by binding straight to the enum.
+ environment.setProperty("OTEL_EXPORTER_OTLP_PROTOCOL", "http/protobuf");
+
+ processor.postProcessEnvironment(environment, new SpringApplication());
+
+ assertThat(environment.getProperty("management.otlp.tracing.transport"))
+ .as("http/protobuf must map to the HTTP transport enum, not the raw spec value")
+ .isEqualTo("http");
+ assertThat(environment.getProperty("management.otlp.tracing.endpoint"))
+ .as("HTTP transport must move off the gRPC :4317 port to the :4318 /v1/traces path")
+ .isEqualTo("http://localhost:4318/v1/traces");
+ }
+
+ @Test
+ void otlpProtocolHttpJsonAlsoTranslatesToHttpTransport() {
+ MockEnvironment environment = new MockEnvironment();
+ environment.setProperty("OTEL_EXPORTER_OTLP_PROTOCOL", "http/json");
+
+ processor.postProcessEnvironment(environment, new SpringApplication());
+
+ assertThat(environment.getProperty("management.otlp.tracing.transport")).isEqualTo("http");
+ }
+
+ @Test
+ void otlpProtocolGrpcKeepsGrpcTransportAndPort() {
+ MockEnvironment environment = new MockEnvironment();
+ environment.setProperty("OTEL_EXPORTER_OTLP_PROTOCOL", "grpc");
+
+ processor.postProcessEnvironment(environment, new SpringApplication());
+
+ assertThat(environment.getProperty("management.otlp.tracing.transport")).isEqualTo("grpc");
+ assertThat(environment.getProperty("management.otlp.tracing.endpoint"))
+ .as("gRPC must keep the default :4317 endpoint")
+ .isEqualTo("http://localhost:4317");
+ }
+
+ @Test
+ void otlpProtocolUnsetLeavesGrpcDefault() {
+ MockEnvironment environment = new MockEnvironment();
+
+ processor.postProcessEnvironment(environment, new SpringApplication());
+
+ assertThat(environment.getProperty("management.otlp.tracing.transport"))
+ .as("No OTel protocol override means the YAML grpc default stands")
+ .isEqualTo("grpc");
+ }
+
+ @Test
+ void explicitOtlpEndpointIsRespectedWhenSwitchingToHttp() {
+ MockEnvironment environment = new MockEnvironment();
+ environment.setProperty("OTEL_EXPORTER_OTLP_PROTOCOL", "http/protobuf");
+ environment.setProperty("OTEL_EXPORTER_OTLP_ENDPOINT", "http://collector.monitoring:4318");
+
+ processor.postProcessEnvironment(environment, new SpringApplication());
+
+ assertThat(environment.getProperty("management.otlp.tracing.transport")).isEqualTo("http");
+ assertThat(environment.getProperty("management.otlp.tracing.endpoint"))
+ .as("An explicit endpoint must win over the derived HTTP default")
+ .isEqualTo("http://collector.monitoring:4318");
+ }
+
@Test
void defaultsLoadingIsIdempotent() {
MockEnvironment environment = new MockEnvironment();