Skip to content

MicrometerHttpClientMetricsRecorder.recordDataReceivedTime does not correctly handle "null" (tag) values #3883

@helmut-hackl-dynatrace

Description

@helmut-hackl-dynatrace

The implementation of MicrometerHttpClientMetricsRecorder.recordDataReceivedTime accepts null values for various arguments:

	void recordDataReceivedTime(SocketAddress remoteAddress, @Nullable String proxyAddress, String uri, String method, String status, Duration time) {
		String address = formatSocketAddress(remoteAddress);
		MeterKey meterKey = new MeterKey(uri, address, proxyAddress, method, status);
		Timer dataReceivedTime = MapUtils.computeIfAbsent(dataReceivedTimeCache, meterKey,
				key -> filter(Timer.builder(name() + DATA_RECEIVED_TIME)
				                   .tags(HttpClientMeters.DataReceivedTimeTags.REMOTE_ADDRESS.asString(), address,
				                         HttpClientMeters.DataReceivedTimeTags.PROXY_ADDRESS.asString(), proxyAddress,
				                         HttpClientMeters.DataReceivedTimeTags.URI.asString(), uri,
				                         HttpClientMeters.DataReceivedTimeTags.METHOD.asString(), method,
				                         HttpClientMeters.DataReceivedTimeTags.STATUS.asString(), status)
				                   .register(REGISTRY)));
		if (dataReceivedTime != null) {
			dataReceivedTime.record(time);
		}
	}

eg. remoteAddress (-> address), ...

but fails when building the tags of it - as ImmutableTag does not allow null values.

ending up in warning logs like:

2025-08-20 12:14:49.471 UTC WARN [reactor-http-epoll-2] AbstractHttpClientMetricsHandler: [4c73c666-80, L:/100.126.16.238:35740 - R:automation-server.srv.plsrv.prod2.dtp.internal.dynatrace.com/10.178.249.144:443] Exception caught while recording metrics.
java.lang.NullPointerException: null
	at java.base/java.util.Objects.requireNonNull(Unknown Source)
	at io.micrometer.core.instrument.ImmutableTag.<init>(ImmutableTag.java:37)
	at io.micrometer.core.instrument.Tag.of(Tag.java:31)
	at io.micrometer.core.instrument.Tags.of(Tags.java:367)
	at io.micrometer.core.instrument.AbstractTimerBuilder.tags(AbstractTimerBuilder.java:60)
	at io.micrometer.core.instrument.Timer$Builder.tags(Timer.java:362)
	at reactor.netty.http.client.MicrometerHttpClientMetricsRecorder.lambda$recordDataSentTime$1(MicrometerHttpClientMetricsRecorder.java:110)
	at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(Unknown Source)
	at reactor.netty.internal.util.MapUtils.computeIfAbsent(MapUtils.java:63)
	at reactor.netty.http.client.MicrometerHttpClientMetricsRecorder.recordDataSentTime(MicrometerHttpClientMetricsRecorder.java:108)
	at reactor.netty.http.client.MicrometerHttpClientMetricsRecorder.recordDataSentTime(MicrometerHttpClientMetricsRecorder.java:97)
	at reactor.netty.http.client.AbstractHttpClientMetricsHandler.recordWrite(AbstractHttpClientMetricsHandler.java:243)
	at reactor.netty.http.client.AbstractHttpClientMetricsHandler.lambda$write$0(AbstractHttpClientMetricsHandler.java:115)
	at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:603)
	at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:570)
	at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:505)
	at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:649)
	at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:638)
	at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:118)
	at io.netty.util.internal.PromiseNotificationUtil.trySuccess(PromiseNotificationUtil.java:48)
	at io.netty.channel.DelegatingChannelPromiseNotifier.operationComplete(DelegatingChannelPromiseNotifier.java:52)
	at io.netty.channel.DelegatingChannelPromiseNotifier.operationComplete(DelegatingChannelPromiseNotifier.java:31)
	at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:603)
	at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:570)
	at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:505)
	at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:649)
	at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:638)
	at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:118)
	at io.netty.util.internal.PromiseNotificationUtil.trySuccess(PromiseNotificationUtil.java:48)
	at io.netty.channel.ChannelOutboundBuffer.safeSuccess(ChannelOutboundBuffer.java:748)
	at io.netty.channel.ChannelOutboundBuffer.remove(ChannelOutboundBuffer.java:303)
	at io.netty.channel.ChannelOutboundBuffer.removeBytes(ChannelOutboundBuffer.java:383)
	at io.netty.channel.epoll.AbstractEpollChannel.doWriteBytes(AbstractEpollChannel.java:364)
	at io.netty.channel.epoll.AbstractEpollStreamChannel.writeBytes(AbstractEpollStreamChannel.java:262)
	at io.netty.channel.epoll.AbstractEpollStreamChannel.doWriteSingle(AbstractEpollStreamChannel.java:473)
	at io.netty.channel.epoll.AbstractEpollStreamChannel.doWrite(AbstractEpollStreamChannel.java:431)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.flush0(AbstractChannel.java:929)
	at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.flush0(AbstractEpollChannel.java:557)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.flush(AbstractChannel.java:893)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.flush(DefaultChannelPipeline.java:1319)
	at io.netty.channel.AbstractChannelHandlerContext.invokeFlush0(AbstractChannelHandlerContext.java:935)
	at io.netty.channel.AbstractChannelHandlerContext.invokeFlush(AbstractChannelHandlerContext.java:921)
	at io.netty.channel.AbstractChannelHandlerContext.flush(AbstractChannelHandlerContext.java:907)
	at io.netty.handler.ssl.SslHandler.forceFlush(SslHandler.java:2304)
	at io.netty.handler.ssl.SslHandler.wrapAndFlush(SslHandler.java:831)
	at io.netty.handler.ssl.SslHandler.flush(SslHandler.java:808)
	at io.netty.channel.AbstractChannelHandlerContext.invokeFlush0(AbstractChannelHandlerContext.java:941)
	at io.netty.channel.AbstractChannelHandlerContext.invokeFlush(AbstractChannelHandlerContext.java:921)
	at io.netty.channel.AbstractChannelHandlerContext.flush(AbstractChannelHandlerContext.java:907)
	at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.flush(CombinedChannelDuplexHandler.java:531)

Expected Behavior

Method can deal with 'null' arguments

Actual Behavior

'null' arguments end up in 'null' tag values that end up in NullPointerExceptions in ImmutableTag

Steps to Reproduce

any null argument when calling the method.

Possible Solution

use NA similar as it's done for the proxyAddress

Your Environment

  • Reactor version(s) used:
  • Other relevant libraries versions (eg. netty, ...): reactor-netty-http-1.2.8
  • JVM version (java -version): Java 21.0.1
  • OS and version (eg. uname -a): linux

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions