From a Spring Boot test I am starting the embedded Tomcat on a random port:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TestApplicationTest {

    @Test
    void contextLoads() {
    }

}

Inside the test scope, I have a configuration file in which I need to get the random port:

@Configuration
public class ConfigurationTest {

  @LocalServerPort
  int serverPort;

}

When I run the test I get an IllegalStateException:

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'local.server.port' in value "${local.server.port}"
    at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:180)
    at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126)
    at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:239)
    at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210)
    at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.lambda$processProperties$0(PropertySourcesPlaceholderConfigurer.java:175)
    at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:936)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1330)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1309)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:656)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:639)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)

If I use ServletWebServerApplicationContext, or @Value("${server.port}") to get the port, everything is working fine.

Spring Boot version: 2.6.3 With Apache CXF version 3.5.0

Comment From: bclozel

Configuration properties are resolved and bound from the environment before the application starts. To get a random port from the OS, you need to start the application and the server, using the configuration properties you've provided.

This is by design and I'm closing this issue as a result.

Comment From: philwebb

It would be nice if we could get @LocalServerPort to work outside of tests.

@mihaelaDev You can use an event listener to grab the port once the web server has started. Depending on what you're trying to do this might work, but be aware that they even fires after beans have been created.

    @EventListener
    void onWebInit(WebServerInitializedEvent event) {
        int port = event.getWebServer().getPort();
    }

Comment From: snicoll

LocalServerPort uses field injection so I don't like the idea of expanding the scope beyond the test class.

Comment From: philwebb

It's an @Value so you can also use it on a constructor.

Comment From: philwebb

We're going to relocate the Local*Port annotations to the spring-boot-test jar to make it clearer that they are only intended for tests.

Comment From: wilkinsona

We have three annotations to move:

  • org.springframework.boot.actuate.autoconfigure.web.server.LocalManagementPort
  • org.springframework.boot.rsocket.context.LocalRSocketServerPort
  • org.springframework.boot.web.server.LocalServerPort

I'm not keen on an actuate package in spring-boot-test. I'm also not sure why LocalRSocketServerPort is in a context package. There's a org.springframework.boot.rsocket.server package and that feels better aligned with the current location of LocalServerPort. With that in mind, how about these new packages in spring-boot-test:

  • org.springframework.boot.test.web.server.LocalServerPort
  • org.springframework.boot.test.web.server.LocalManagementPort
  • org.springframework.boot.test.rsocket.LocalRSocketServerPort

Comment From: mihaelaDev

Maybe it was not clear that my configuration class is not productive code. I need the @LocalServerPort inside a configuration class that is in test scope:

  • Inside the productive code: I have an SOAP CXF service implemented.
  • Inside the Test Sources Root folder: I have the @SpringBootTest class and the @Configuration file.

I want to start a @SpringBootTest with webEnvironment = RANDOM_PORT in which I want to integartion test the SOAP CXF service. In order to be able to do this, along the test, I have a @Configuration file to define the SOAP service client. In order to define the client I need the server port:

@Configuration
public class ConfigurationTest {

  @LocalServerPort
  private int randomServerPort;

  @Bean
  CXFSoapServiceMyService testBean() {
    final String address = String.format("http://localhost:%d/MyService", randomServerPort);
    JaxWsProxyFactoryBean factoryBean = new JaxWsProxyFactoryBean();
    factoryBean.setAddress(address);
    return factoryBean.create(CXFSoapServiceMyService.class);
  }
}

Please find attached a project example with my use case.

test.zip

Comment From: wilkinsona

@mihaelaDev The annotation can only be used for injection into something that's created after the web server has been started which happens right at the end of application context creation. Typically, this means injection directly into the test class itself. Your ConfigurationTest is created during application context refresh and, crucially, before the web server has been started and its port number is available.

One way to get this to work as you'd like is to defer the creation of your CXFSoapServiceMyService bean by marking it as @Lazy and inject the local server port into the bean method:

@Bean
@Lazy
CXFSoapServiceMyService testBean(@LocalServerPort int randomServerPort) {
  final String address = String.format("http://localhost:%d/MyService", randomServerPort);
  JaxWsProxyFactoryBean factoryBean = new JaxWsProxyFactoryBean();
  factoryBean.setAddress(address);
  return factoryBean.create(CXFSoapServiceMyService.class);
}

In your sample at least, this defers the need for local.server.port to have been set until after the web server has been started.

Comment From: mihaelaDev

@mihaelaDev The annotation can only be used for injection into something that's created after the web server has been started which happens right at the end of application context creation. Typically, this means injection directly into the test class itself. Your ConfigurationTest is created during application context refresh and, crucially, before the web server has been started and its port number is available.

One way to get this to work as you'd like is to defer the creation of your CXFSoapServiceMyService bean by marking it as @Lazy and inject the local server port into the bean method:

java @Bean @Lazy CXFSoapServiceMyService testBean(@LocalServerPort int randomServerPort) { final String address = String.format("http://localhost:%d/MyService", randomServerPort); JaxWsProxyFactoryBean factoryBean = new JaxWsProxyFactoryBean(); factoryBean.setAddress(address); return factoryBean.create(CXFSoapServiceMyService.class); }

In your sample at least, this defers the need for local.server.port to have been set until after the web server has been started.

Now it's clear. Thank you @wilkinsona ! I'll use your suggestion. Thank you all for the support!

Comment From: mohamedmiranfazil29

Excellent work