Spring Boot - How to get the running port
Asked Answered
P

14

141

I have a spring boot application (using embedded tomcat 7), and I've set server.port = 0 in my application.properties so I can have a random port. After the server is booted up and running on a port, I need to be able to get the port that that was chosen.

I cannot use @Value("$server.port") because it's zero. This is a seemingly simple piece of information, so why can't I access it from my java code? How can I access it?

Portugal answered 18/5, 2015 at 20:14 Comment(2)
Related: https://mcmap.net/q/161621/-how-to-find-port-of-spring-boot-container-when-running-a-spock-test-using-property-server-port-0Kirsti
Another possibility can be found in the docs: docs.spring.io/spring-boot/docs/current/reference/html/… (see 64.5 Discover the HTTP port at runtime )Kirsti
P
30

Thanks to @Dirk Lachowski for pointing me in the right direction. The solution isn't as elegant as I would have liked, but I got it working. Reading the spring docs, I can listen on the EmbeddedServletContainerInitializedEvent and get the port once the server is up and running. Here's what it looks like -

import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;




    @Component
    public class MyListener implements ApplicationListener<EmbeddedServletContainerInitializedEvent> {

      @Override
      public void onApplicationEvent(final EmbeddedServletContainerInitializedEvent event) {
          int thePort = event.getEmbeddedServletContainer().getPort();
      }
    }
Portugal answered 18/5, 2015 at 21:20 Comment(5)
AFAIK this won't work if you're looking to configure a bean with the server port. This event isn't fired off until after all the beans have been loaded and the servlets have been registered.Valarievalda
it worked for me at the time thats why I accepted it. I haven't tried hennr's answer though.Portugal
After reading the docs I came up with virtually the same small class as you, naming it PortProvider, and providing a getPort() method. Autowired my PortProvider in to the controller needing the port, and when my business logic called portProvider.getPort(), the runtime port was returned.Earthquake
For anyone trying this with Spring Boot 2.0 or later, the API seems to have changed slightly. I was no longer able to subscribe to EmbeddedServletContainerInitializedEvent, but there is a similar class called ServletWebServerInitializedEvent which has a .getWebServer() method. This will get you the port Tomcat is listening to at least.Fowling
Since spring 4.2, there is now an @EventListener annotation for a cleaner API also allowing multiple events to be easily handled. See https://mcmap.net/q/161622/-is-it-possible-to-make-a-spring-applicationlistener-listen-for-2-or-more-types-of-eventsTreytri
R
131

Is it also possible to access the management port in a similar way, e.g.:

  @SpringBootTest(classes = {Application.class}, webEnvironment = WebEnvironment.RANDOM_PORT)
  public class MyTest {

    @LocalServerPort
    int randomServerPort;

    @LocalManagementPort
    int randomManagementPort;
Regain answered 11/8, 2016 at 11:15 Comment(4)
@LocalServerPort is just a shortcut for @Value("${local.server.port}").Heartbroken
@Heartbroken means if you not specify local.server.port in properties - it wont worksUltann
Using webEnvironment = WebEnvironment.RANDOM_PORT resolved the issue. ThanksHoskinson
Could not resolve placeholder 'local.server.port' in value "${local.server.port}"Adige
S
103

Spring's Environment holds this information for you.

@Autowired
Environment environment;

String port = environment.getProperty("local.server.port");

On the surface this looks identical to injecting a field annotated @Value("${local.server.port}") (or @LocalServerPort, which is identical), whereby an autowiring failure is thrown at startup as the value isn't available until the context is fully initialised. The difference here is that this call is implicitly being made in runtime business logic rather than invoked at application startup, and hence the 'lazy-fetch' of the port resolves ok.

Screwworm answered 3/4, 2016 at 21:2 Comment(3)
for some reason this didnt work for me, environment.getProperty("server.port") did.Canvass
in my case port is null. @RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) @ActiveProfiles("test") public class SpringBootH2IntegrationTest { @Autowired Environment environment; @Test public void test() { String port = environment.getProperty("local.server.port"); // null here } After adding: webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) next to SpringBootTest I see port.Hafner
This is because @SpringBootTest defaults to a MockMVC environment, not starting an apllication server that could provide a port.Screwworm
P
30

Thanks to @Dirk Lachowski for pointing me in the right direction. The solution isn't as elegant as I would have liked, but I got it working. Reading the spring docs, I can listen on the EmbeddedServletContainerInitializedEvent and get the port once the server is up and running. Here's what it looks like -

import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;




    @Component
    public class MyListener implements ApplicationListener<EmbeddedServletContainerInitializedEvent> {

      @Override
      public void onApplicationEvent(final EmbeddedServletContainerInitializedEvent event) {
          int thePort = event.getEmbeddedServletContainer().getPort();
      }
    }
Portugal answered 18/5, 2015 at 21:20 Comment(5)
AFAIK this won't work if you're looking to configure a bean with the server port. This event isn't fired off until after all the beans have been loaded and the servlets have been registered.Valarievalda
it worked for me at the time thats why I accepted it. I haven't tried hennr's answer though.Portugal
After reading the docs I came up with virtually the same small class as you, naming it PortProvider, and providing a getPort() method. Autowired my PortProvider in to the controller needing the port, and when my business logic called portProvider.getPort(), the runtime port was returned.Earthquake
For anyone trying this with Spring Boot 2.0 or later, the API seems to have changed slightly. I was no longer able to subscribe to EmbeddedServletContainerInitializedEvent, but there is a similar class called ServletWebServerInitializedEvent which has a .getWebServer() method. This will get you the port Tomcat is listening to at least.Fowling
Since spring 4.2, there is now an @EventListener annotation for a cleaner API also allowing multiple events to be easily handled. See https://mcmap.net/q/161622/-is-it-possible-to-make-a-spring-applicationlistener-listen-for-2-or-more-types-of-eventsTreytri
T
18

You can get the port that is being used by an embedded Tomcat instance during tests by injecting the local.server.port value as such:

// Inject which port we were assigned
@Value("${local.server.port}")
int port;
Thereabouts answered 6/7, 2015 at 20:12 Comment(2)
local.server.port is only set when running with @WebIntegrationTestsHypodermic
WebIntegrationTest is Deprecated.Joker
I
17

Just so others who have configured their apps like mine benefit from what I went through...

None of the above solutions worked for me because I have a ./config directory just under my project base with 2 files:

application.properties
application-dev.properties

In application.properties I have:

spring.profiles.active = dev  # set my default profile to 'dev'

In application-dev.properties I have:

server_host = localhost
server_port = 8080

This is so when I run my fat jar from the CLI the *.properties files will be read from the ./config dir and all is good.

Well, it turns out that these properties files completely override the webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT setting in @SpringBootTest in my Spock specs. No matter what I tried, even with webEnvironment set to RANDOM_PORT Spring would always startup the embedded Tomcat container on port 8080 (or whatever value I'd set in my ./config/*.properties files).

The ONLY way I was able to overcome this was by adding an explicit properties = "server_port=0" to the @SpringBootTest annotation in my Spock integration specs:

@SpringBootTest (webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "server_port=0")

Then, and only then did Spring finally start to spin up Tomcat on a random port. IMHO this is a Spring testing framework bug, but I'm sure they'll have their own opinion on this.

Hope this helped someone.

Indicative answered 3/5, 2017 at 0:36 Comment(1)
Have this exact same setup and also ran into this. I assumed this was the issue in some sense, but thanks for posting your solution here. Do you know if anyone has logged this as a bug yet?Gerge
G
12

Starting with Spring Boot 1.4.0 you can use this in your test:

import org.springframework.boot.context.embedded.LocalServerPort;

@SpringBootTest(classes = {Application.class}, webEnvironment = WebEnvironment.RANDOM_PORT)
public class MyTest {

  @LocalServerPort
  int randomPort;

  // ...
}
Goldia answered 5/8, 2016 at 21:17 Comment(0)
I
11

After Spring Boot 2, a lot has changed. The above given answers work prior to Spring Boot 2. Now if you are running your application with runtime arguments for the server port, then you will only get the static value with @Value("${server.port}"), that is mentioned in the application.properties file. Now to get the actual port in which the server is running, use the following method:

    @Autowired
    private ServletWebServerApplicationContext server;

    @GetMapping("/server-port")
    public String serverPort() {

        return "" + server.getWebServer().getPort();
    }

Also, if you are using your applications as Eureka/Discovery Clients with load balanced RestTemplate or WebClient, the above method will return the exact port number.

Indigotin answered 19/8, 2020 at 12:30 Comment(3)
This is the right answer for Spring Boot 2. Works fine with @SpringBootTest and WebEnvironment.RANDOM_PORT.Addiction
Just to supplement the comment of @Ken Pronovici - if webEnvironment parameter is not specified in SpringBootTest annotation, the Spring doesn't create the ServletWebServerApplicationContext and fails at runtime because it can't find a candidate bean to injectBird
Tried this and always 0 to me.Roadbed
V
8

None of these solutions worked for me. I needed to know the server port while constructing a Swagger configuration bean. Using ServerProperties worked for me:

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.ws.rs.ApplicationPath;

import io.swagger.jaxrs.config.BeanConfig;
import io.swagger.jaxrs.listing.ApiListingResource;
import io.swagger.jaxrs.listing.SwaggerSerializers;

import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;

@Component
@ApplicationPath("api")
public class JerseyConfig extends ResourceConfig 
{
    @Inject
    private org.springframework.boot.autoconfigure.web.ServerProperties serverProperties;

    public JerseyConfig() 
    {
        property(org.glassfish.jersey.server.ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
    }

    @PostConstruct
    protected void postConstruct()
    {
        // register application endpoints
        registerAndConfigureSwaggerUi();
    }

    private void registerAndConfigureSwaggerUi()
    {
        register(ApiListingResource.class);
        register(SwaggerSerializers.class);

        final BeanConfig config = new BeanConfig();
        // set other properties
        config.setHost("localhost:" + serverProperties.getPort()); // gets server.port from application.properties file         
    }
}

This example uses Spring Boot auto configuration and JAX-RS (not Spring MVC).

Valarievalda answered 6/4, 2016 at 20:35 Comment(1)
I wanted the same thing for swaggerTevis
L
3

I'm in Spring 2.5.5 and use Junit 4.13.2, here is my solution:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit4.SpringRunner;

// tell Java the environment your testcase running is in Spring, 
// which will enable the auto configuration such as value injection
@RunWith(SpringRunner.class)
@SpringBootTest(
    class = Application.class, 
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SimpleWebTest {

    @LocalServerPort
    private int randomPort;

    @Test
    public void test() {
        // use randomPort ...
        System.out.println(randomPort);
    }

}
Laryngo answered 16/10, 2021 at 13:14 Comment(1)
Deprecated since 2.7.0 for removal in 3.0.0Vase
I
1

Please make sure you have imported the correct package

import org.springframework.core.env.Environment;

and then use the Environment object

@Autowired
private Environment env;    // Environment Object containts the port number

 @GetMapping("/status")
  public String status()
    {
   return "it is runing on"+(env.getProperty("local.server.port"));
    }
Irfan answered 21/1, 2020 at 21:13 Comment(1)
I have made changes to my answer are you still having the same issue?Irfan
F
1

You can get the server port from the

HttpServletRequest
@Autowired
private HttpServletRequest request;

@GetMapping(value = "/port")
public Object getServerPort() {
   System.out.println("I am from " + request.getServerPort());
   return "I am from  " + request.getServerPort();
}
    
Formulism answered 15/10, 2020 at 17:39 Comment(2)
I think that's a bad idea. The information can be retrieved when a request ist made. Probably one wants to know the port on startup before the first request is made.Vaud
Another issue which could be severe if wrongly adapted is that the HttpServletRequest is set as a private member variable of a controller class. When having two request at the 'same' time the setting of 'request' will be overwritten since the class is a singleton (isn't it? - let me know) If it was thread-wise the implementation would be ok. (also see: https://mcmap.net/q/161623/-stateful-beans-and-stateless-beans-in-spring-context)Vaud
U
1

With Spring Boot 3.0 listening for ServletWebServerInitializedEvent event seems to be the best idea, as the server TCP port becomes to be known at some certain point in time. No earlier!

So you can add such a bean:

    @Bean
    public ApplicationListener<ServletWebServerInitializedEvent> serverPortListenerBean() {
        return event -> {
            int serverPort = event.getWebServer().getPort();
            // TODO do something with the `serverPort`
        };
    }
Underpin answered 14/5, 2023 at 21:51 Comment(0)
S
0

The below code works only in case you have local.server.port in application.properties.

@LocalServerPort
int randomServerPort;

it's equal to

@Value("{local.server.port}")
int randomServerPort;

the @LocalServerPort annotation is depreciated in springboot3

As server.port is a native property in springboot instead of local.server.port, you can simply use

@Value("{server.port}")
int randomServerPort;

just make sure you have something like server.port=8080 in application.properties

Sepaloid answered 14/2 at 7:51 Comment(0)
C
-1

I solved it with a kind of proxy bean. The client gets initialized when it is needed, by then the port should be available:

@Component
public class GraphQLClient {

    private ApolloClient apolloClient;
    private final Environment environment;

    public GraphQLClient(Environment environment) {
        this.environment = environment;
    }

    public ApolloClient getApolloClient() {
        if (apolloClient == null) {
            String port = environment.getProperty("local.server.port");
            initApolloClient(port);
        }
        return apolloClient;
    }

    public synchronized void initApolloClient(String port) {
        this.apolloClient = ApolloClient.builder()
                .serverUrl("http://localhost:" + port + "/graphql")
                .build();
    }

    public <D extends Operation.Data, T, V extends Operation.Variables> GraphQLCallback<T> graphql(Operation<D, T, V> operation) {
        GraphQLCallback<T> graphQLCallback = new GraphQLCallback<>();
        if (operation instanceof Query) {
            Query<D, T, V> query = (Query<D, T, V>) operation;
            getApolloClient()
                    .query(query)
                    .enqueue(graphQLCallback);
        } else {
            Mutation<D, T, V> mutation = (Mutation<D, T, V>) operation;
            getApolloClient()
                    .mutate(mutation)
                    .enqueue(graphQLCallback);

        }
        return graphQLCallback;
    }
}
Contour answered 25/10, 2020 at 18:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.