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?
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;
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.
environment.getProperty("server.port")
did.
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.
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();
}
}
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.
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.
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;
local.server.port
is only set when running with @WebIntegrationTests
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.
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;
// ...
}
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).
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.
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();
}
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);
}
}
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"));
}
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;
}
}
Success story sharing
@LocalServerPort
is just a shortcut for@Value("${local.server.port}")
.webEnvironment = WebEnvironment.RANDOM_PORT
resolved the issue. Thanks