Underscores are valid in URIs https://www.ietf.org/rfc/rfc3986.txt but has long been ignored in jdk https://bugs.openjdk.java.net/browse/JDK-8019345

Could we provide a Spring URI class (and supporting classes like UriComponentsBuilder) that supports this?

URI uri = UriComponentsBuilder.fromUriString("https://endpoint_with_underscore").build().toUri();

results in

java.lang.IllegalStateException: Could not create URI object: Illegal character in hostname at index 16: https://endpoint_with_underscore

    at org.springframework.web.util.HierarchicalUriComponents.toUri(HierarchicalUriComponents.java:516)
    at org.springframework.cloud.gateway.filter.ForwardRoutingFilterTests.uriWithUnderscore(ForwardRoutingFilterTests.java:75)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.mockito.internal.runners.DefaultInternalRunner$1$1.evaluate(DefaultInternalRunner.java:44)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:74)
    at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:80)
    at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:39)
    at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: java.net.URISyntaxException: Illegal character in hostname at index 16: https://endpoint_with_underscore
    at java.net.URI$Parser.fail(URI.java:2848)
    at java.net.URI$Parser.parseHostname(URI.java:3387)
    at java.net.URI$Parser.parseServer(URI.java:3236)
    at java.net.URI$Parser.parseAuthority(URI.java:3155)
    at java.net.URI$Parser.parseHierarchical(URI.java:3097)
    at java.net.URI$Parser.parse(URI.java:3053)
    at java.net.URI.<init>(URI.java:673)
    at org.springframework.web.util.HierarchicalUriComponents.toUri(HierarchicalUriComponents.java:512)
    ... 29 more

Various issues: - https://github.com/r2dbc/r2dbc-spi/issues/155 - https://github.com/spring-cloud/spring-cloud-gateway/issues/851 - https://github.com/spring-cloud/spring-cloud-gateway/issues/543 - https://github.com/spring-cloud/spring-cloud-netflix/issues/263 - https://github.com/spring-cloud/spring-cloud-commons/issues/159

Comment From: rstoyanchev

@spencergibb isn't that what UriComponents is?

/**
 * Represents an immutable collection of URI components, mapping component type to
 * String values. Contains convenience getters for all components. Effectively similar
 * to java.net.URI, but with more powerful encoding options and support for
 * URI template variables.
 */

The UriComponents#toUri() is primarily for when you need to pass java.net.URI somewhere but if you just need a parsed representation, you already have one.

Comment From: spencergibb

@rstoyanchev I hadn't even considered it. Looking at how gateway uses a uri, whenever it needs to forward it converts to a string and where it combines url's (ie from request and the information from service discovery) it is using UriComponentsBuilder. I think for gateway this would work fine.

Where it won't work is for RestTemplate and WebClient via ClientHttpRequestInterceptor and ExchangeFilterFunction respectively since the request builders all take URI.

Comment From: rstoyanchev

I think for gateway this would work fine.

Great!

Where it won't work is for RestTemplate and WebClient

This would be harder because it's built into contracts like ClientHttpConnector and underneath it also depends on the underlying HTTP client. Reactor Netty client seems to avoid java.net.URI for the most part except for WebSocket. Jetty HttpClient does accept String but turns it into URI.create(String) immediately.

Comment From: rstoyanchev

In summary, we already provide UriComponents as an alternative to java.net.URI but we can't completely replace its use in lower level libraries we depend on. This can only be addressed in the JDK.